

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=dark>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/memory_blog/img/fluid.png">
  <link rel="icon" href="/memory_blog/img/icon/dw.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="Memory">
  <meta name="keywords" content="">
  
    <meta name="description" content="MemorySearch 忆搜阁开发基本流程">
<meta property="og:type" content="article">
<meta property="og:title" content="MemorySearch 忆搜阁-开发文档">
<meta property="og:url" content="https://test.atomgit.net/memory_blog/2023/08/26/MemorySearch%20%E5%BF%86%E6%90%9C%E9%98%81-%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/index.html">
<meta property="og:site_name" content="Memory&#39;s blog">
<meta property="og:description" content="MemorySearch 忆搜阁开发基本流程">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://test.atomgit.net/memory_blog/img/artical/ren.jpg">
<meta property="article:published_time" content="2023-08-26T14:09:22.000Z">
<meta property="article:modified_time" content="2024-02-19T12:22:54.000Z">
<meta property="article:author" content="Memory">
<meta property="article:tag" content="项目">
<meta property="article:tag" content="开发经验">
<meta property="article:tag" content="Elasticsearch">
<meta property="article:tag" content="Logstash">
<meta property="article:tag" content="Kibana">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://test.atomgit.net/memory_blog/img/artical/ren.jpg">
  
  
  
  <title>MemorySearch 忆搜阁-开发文档 - Memory&#39;s blog</title>

  <link  rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />



  <link  rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css" />



<!-- 主题依赖的图标库，不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_hj8rtnfg7um.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/memory_blog/css/main.css" />


  <link id="highlight-css" rel="stylesheet" href="/memory_blog/css/highlight.css" />
  
    <link id="highlight-css-dark" rel="stylesheet" href="/memory_blog/css/highlight-dark.css" />
  




  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    Fluid.ctx = Object.assign({}, Fluid.ctx)
    var CONFIG = {"hostname":"test.atomgit.net","root":"/memory_blog/","version":"1.9.4","typing":{"enable":true,"typeSpeed":100,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"right","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":false,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":true,"follow_dnt":true,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/memory_blog/local-search.xml"};

    if (CONFIG.web_analytics.follow_dnt) {
      var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
      Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
    }
  </script>
  <script  src="/memory_blog/js/utils.js" ></script>
  <script  src="/memory_blog/js/color-schema.js" ></script>
  

  

  

  

  

  

  

  



  
<meta name="generator" content="Hexo 5.4.2"></head>


<body>
  

  <header>
    

<div class="header-inner" style="height: 75vh;">
  <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/memory_blog/">
      <strong>Memory&#39;s blog</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/memory_blog/">
                <i class="iconfont icon-home-fill"></i>
                <span>首页</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/memory_blog/archives/">
                <i class="iconfont icon-archive-fill"></i>
                <span>归档</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/memory_blog/categories/">
                <i class="iconfont icon-category-fill"></i>
                <span>分类</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/memory_blog/tags/">
                <i class="iconfont icon-tags-fill"></i>
                <span>标签</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/memory_blog/about/">
                <i class="iconfont icon-user-fill"></i>
                <span>关于</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/memory_blog/links/">
                <i class="iconfont icon-link-fill"></i>
                <span>友链</span>
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              <i class="iconfont icon-search"></i>
            </a>
          </li>
          
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">
              <i class="iconfont icon-dark" id="color-toggle-icon"></i>
            </a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

  

<div id="banner" class="banner" parallax=true
     style="background: url('/memory_blog/img/newBG/girl.jpg') no-repeat center center; background-size: cover;">
  <div class="full-bg-img">
    <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
      <div class="banner-text text-center fade-in-up">
        <div class="h2">
          
            <span id="subtitle" data-typed-text="MemorySearch 忆搜阁-开发文档"></span>
          
        </div>

        
          
  <div class="mt-3">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-author" aria-hidden="true"></i>
        Memory
      </span>
    
    
      <span class="post-meta">
        <i class="iconfont icon-date-fill" aria-hidden="true"></i>
        <time datetime="2023-08-26 22:09" pubdate>
          2023年8月26日 晚上
        </time>
      </span>
    
  </div>

  <div class="mt-1">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-chart"></i>
        
          102k 字
        
      </span>
    

    
      <span class="post-meta mr-2">
        <i class="iconfont icon-clock-fill"></i>
        
        
        
          255 分钟
        
      </span>
    

    
    
      
        <span id="leancloud-page-views-container" class="post-meta" style="display: none">
          <i class="iconfont icon-eye" aria-hidden="true"></i>
          <span id="leancloud-page-views"></span> 次
        </span>
        
      
    
  </div>


        
      </div>

      
    </div>
  </div>
</div>

</div>

  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar category-bar" style="margin-right: -1rem">
    





<div class="category-list">
  
  
    
    
    
    <div class="category row nomargin-x">
      <a class="category-item 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="项目"
        id="heading-31ecc0e65b4cd3e6b12577382bb56d6a" role="tab" data-toggle="collapse" href="#collapse-31ecc0e65b4cd3e6b12577382bb56d6a"
        aria-expanded="true"
      >
        项目
        <span class="list-group-count">(6)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-31ecc0e65b4cd3e6b12577382bb56d6a"
           role="tabpanel" aria-labelledby="heading-31ecc0e65b4cd3e6b12577382bb56d6a">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="开发经验"
        id="heading-bfd19ce0478d81a44ceb5a9a1aa94202" role="tab" data-toggle="collapse" href="#collapse-bfd19ce0478d81a44ceb5a9a1aa94202"
        aria-expanded="true"
      >
        开发经验
        <span class="list-group-count">(5)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-bfd19ce0478d81a44ceb5a9a1aa94202"
           role="tabpanel" aria-labelledby="heading-bfd19ce0478d81a44ceb5a9a1aa94202">
        
        
          
          
  <div class="category-post-list">
    
    
      
      
        <a href="/memory_blog/2023/03/17/Memory%20%E7%94%A8%E6%88%B7%E4%B8%AD%E5%BF%83%E7%B3%BB%E7%BB%9F-%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="Memory 用户中心中心-开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">Memory 用户中心中心-开发文档</span>
        </a>
      
    
      
      
        <a href="/memory_blog/2023/06/20/Typora%E6%93%8D%E4%BD%9C%E6%8A%80%E5%B7%A7/" title="美轮美奂的文档艺术：Typora的使用技巧和实践"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">美轮美奂的文档艺术：Typora的使用技巧和实践</span>
        </a>
      
    
      
      
        <a href="/memory_blog/2023/08/02/PicMemories%20%E5%A3%81%E7%BA%B8%E5%88%86%E4%BA%AB%E5%B0%8F%E7%A8%8B%E5%BA%8F%20-%20%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="PicMemories-开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">PicMemories-开发文档</span>
        </a>
      
    
      
      
        <a href="/memory_blog/2023/08/07/TaskMemories%20-%20%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="TaskMemories-开发文档"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">TaskMemories-开发文档</span>
        </a>
      
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="Elasticsearch"
        id="heading-45e23a169652aaf95ce80da844f3df0d" role="tab" data-toggle="collapse" href="#collapse-45e23a169652aaf95ce80da844f3df0d"
        aria-expanded="true"
      >
        Elasticsearch
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-45e23a169652aaf95ce80da844f3df0d"
           role="tabpanel" aria-labelledby="heading-45e23a169652aaf95ce80da844f3df0d">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="Logstash"
        id="heading-65859d7d6b0c6a5327b8f0b0c09653ad" role="tab" data-toggle="collapse" href="#collapse-65859d7d6b0c6a5327b8f0b0c09653ad"
        aria-expanded="true"
      >
        Logstash
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-65859d7d6b0c6a5327b8f0b0c09653ad"
           role="tabpanel" aria-labelledby="heading-65859d7d6b0c6a5327b8f0b0c09653ad">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem 
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="Kibana"
        id="heading-5228fdd6f613af63fc73ee7c02ef8fb5" role="tab" data-toggle="collapse" href="#collapse-5228fdd6f613af63fc73ee7c02ef8fb5"
        aria-expanded="true"
      >
        Kibana
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse show" id="collapse-5228fdd6f613af63fc73ee7c02ef8fb5"
           role="tabpanel" aria-labelledby="heading-5228fdd6f613af63fc73ee7c02ef8fb5">
        
        
          
  <div class="category-post-list">
    
    
      
      
        <a href="/memory_blog/2023/08/26/MemorySearch%20%E5%BF%86%E6%90%9C%E9%98%81-%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="MemorySearch 忆搜阁-开发文档"
           class="list-group-item list-group-item-action
           active">
          <span class="category-post">MemorySearch 忆搜阁-开发文档</span>
        </a>
      
    
  </div>

        
      </div>
    </div>
  
        
      </div>
    </div>
  
        
      </div>
    </div>
  
        
      </div>
    </div>
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="项目部署"
        id="heading-50be5e3b1697e929a38352262896962e" role="tab" data-toggle="collapse" href="#collapse-50be5e3b1697e929a38352262896962e"
        aria-expanded="false"
      >
        项目部署
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-50be5e3b1697e929a38352262896962e"
           role="tabpanel" aria-labelledby="heading-50be5e3b1697e929a38352262896962e">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="开发经验"
        id="heading-bfd19ce0478d81a44ceb5a9a1aa94202" role="tab" data-toggle="collapse" href="#collapse-bfd19ce0478d81a44ceb5a9a1aa94202"
        aria-expanded="false"
      >
        开发经验
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-bfd19ce0478d81a44ceb5a9a1aa94202"
           role="tabpanel" aria-labelledby="heading-bfd19ce0478d81a44ceb5a9a1aa94202">
        
        
          
          
  <div class="category-post-list">
    
    
  </div>

          
  
    
    
    
    <div class="category-sub row nomargin-x">
      <a class="category-subitem collapsed
          list-group-item category-item-action col-10 col-md-11 col-xm-11" title="网站"
        id="heading-beb6c0bd8a66cea600c8ce40167eeb57" role="tab" data-toggle="collapse" href="#collapse-beb6c0bd8a66cea600c8ce40167eeb57"
        aria-expanded="false"
      >
        网站
        <span class="list-group-count">(1)</span>
        <i class="iconfont icon-arrowright"></i>
      </a>
      
      <div class="category-collapse collapse " id="collapse-beb6c0bd8a66cea600c8ce40167eeb57"
           role="tabpanel" aria-labelledby="heading-beb6c0bd8a66cea600c8ce40167eeb57">
        
        
          
  <div class="category-post-list">
    
    
      
      
        <a href="/memory_blog/2023/06/04/%E6%8F%AD%E7%A7%98%E9%A1%B9%E7%9B%AE%E9%83%A8%E7%BD%B2%E4%B8%8A%E7%BA%BF%E4%B9%8B%E8%B7%AF%EF%BC%9A%E6%B5%81%E7%A8%8B%E3%80%81%E6%8C%91%E6%88%98%E4%B8%8E%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/" title="揭秘项目部署上线之路：流程、挑战与解决方案"
           class="list-group-item list-group-item-action
           ">
          <span class="category-post">揭秘项目部署上线之路：流程、挑战与解决方案</span>
        </a>
      
    
  </div>

        
      </div>
    </div>
  
        
      </div>
    </div>
  
        
      </div>
    </div>
  
        
      </div>
    </div>
  
</div>


  </aside>


    </div>

    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">MemorySearch 忆搜阁-开发文档</h1>
            
              <p class="note note-success">
                
                  
                    本文最后更新于：5 个月前
                  
                
              </p>
            
            
              <div class="markdown-body">
                
                <p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/girl3_ps.jpg" alt="生活的每一个瞬间都是独一无二的，让我们用心去感受，去珍惜。"></p>
<h1 id="MemorySearch-忆搜阁"><a href="#MemorySearch-忆搜阁" class="headerlink" title="MemorySearch 忆搜阁"></a>MemorySearch 忆搜阁</h1><p>前端代码仓库：<a target="_blank" rel="noopener" href="https://gitee.com/deng-2022/memory-search-frontend">memory-search-frontend</a></p>
<h2 id="☕-项目概述"><a href="#☕-项目概述" class="headerlink" title="☕ 项目概述"></a>☕ 项目概述</h2><p>这个项目是一个基于 <strong>Spring Boot + Elastic Stack 技术栈 + Vue.js</strong> 的<code>聚合搜索中台</code>。它不仅是一个强大的搜索引擎，更是一个内容丰富的社区平台。</p>
<p>这个项目的目标是提供一个<strong>一站式的搜索、管理和互动体验</strong>，满足各种用户需求。</p>
<h2 id="🥘-效果展示"><a href="#🥘-效果展示" class="headerlink" title="🥘 效果展示"></a>🥘 效果展示</h2><h3 id="用户登录"><a href="#用户登录" class="headerlink" title="用户登录"></a>用户登录</h3><p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240222202351543.png" alt="image-20240222202351543"></p>
<h3 id="图片搜索"><a href="#图片搜索" class="headerlink" title="图片搜索"></a>图片搜索</h3><p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240222203543310.png" alt="image-20240222203543310"></p>
<h3 id="文章上传"><a href="#文章上传" class="headerlink" title="文章上传"></a>文章上传</h3><p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240222203451920.png" alt="image-20240222203451920"></p>
<h3 id="统计分析"><a href="#统计分析" class="headerlink" title="统计分析"></a>统计分析</h3><p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240222213338497.png" alt="image-20240222213338497"></p>
<h2 id="🍚-使用场景"><a href="#🍚-使用场景" class="headerlink" title="🍚 使用场景"></a>🍚 使用场景</h2><ul>
<li> 企业内部多项目数据搜索：该平台能够满足企业内部多个项目的数据搜索需求，避免每个项目都单独开发搜索功能，提升开发效率并降低系统维护成本。</li>
<li> 多源内容聚合搜索：当需要聚合不同来源、不同类型的内容时，该平台可以提供一站式的搜索页面，便于用户快速查找所需信息，提高工作效率。</li>
<li> 企业级搜索需求：对于有大规模搜索需求的企业，该平台提供了稳定的、高效的搜索功能，满足企业的搜索需求，并支持数据源接入和管理。</li>
</ul>
<h2 id="🥣-核心功能与特点"><a href="#🥣-核心功能与特点" class="headerlink" title="🥣 核心功能与特点"></a>🥣 核心功能与特点</h2><ul>
<li><p> <strong>高效多元搜索</strong> ：用户可以在搜索框中输入关键词，系统会提供快速、准确的搜索结果。搜索结果会根据内容类型（文本、图片、视频）进行分类展示，并提供关键词高亮和搜索建议，使用户能快速找到所需内容。</p>
</li>
<li><p> <strong>互动创作平台</strong> ：用户可以在这个模块中发布文章、上传图片，与其他用户互动。系统会自动推荐热门内容，引导用户发现更多优质内容。用户还可以对文章、图片进行点赞、评论和收藏，形成一个活跃的内容创作社区。</p>
</li>
<li><p> <strong>流量统计分析</strong> ：系统会自动统计每个关键词的搜索流量，并按照时间、关键词类型等维度进行分析。用户可以查看热搜词类别和搜索流量高峰，了解内容趋势和用户行为。</p>
</li>
<li><p> <strong>个人中心管理</strong> ：用户可以在个人中心查看和编辑个人信息，包括头像、昵称、简介等。用户还可以查看自己的点赞、评论和收藏的内容，以及自己创作的文章和下载的图片、视频等。</p>
</li>
<li><p> <strong>资源全面管理</strong> ：这个模块仅对管理员可见，管理员可以对全站资源（文章、图片、视频、用户等）进行全面管理。管理员可以对资源进行添加、删除、修改等操作，保证资源的准确性和完整性。</p>
</li>
<li><p> <strong>图片预览分享</strong> ：通过集成的图片预览功能，用户可以像浏览相册一样查看页面中的图片，并支持缩放和分享到社交媒体平台。</p>
</li>
</ul>
<h2 id="🍜-访问地址"><a href="#🍜-访问地址" class="headerlink" title="🍜 访问地址"></a>🍜 访问地址</h2><p>暂未部署上线，点击跳转至：<a target="_blank" rel="noopener" href="https://deng-2022.gitee.io/blog/2023/08/26/Memory%20%E8%81%9A%E5%90%88%E6%90%9C%E7%B4%A2%E5%B9%B3%E5%8F%B0-%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/">个人博客 MemorySearch 开发文档</a></p>
<h2 id="🍝-架构设计"><a href="#🍝-架构设计" class="headerlink" title="🍝 架构设计"></a>🍝 架构设计</h2><p>原图链接：<a target="_blank" rel="noopener" href="https://www.yuque.com/huiyiruchu-7jxo6/ckx99i/lxw5okzip8hktuln?singleDoc#">项目架构图</a></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240221231907060.png" alt="image-20240221231907060"></p>
<h2 id="🍺-技术选型"><a href="#🍺-技术选型" class="headerlink" title="🍺 技术选型"></a>🍺 技术选型</h2><h3 id="后端"><a href="#后端" class="headerlink" title="后端"></a>后端</h3><ul>
<li>Spring Boot：作为项目的核心框架，提供快速构建 RESTful API 的能力。</li>
<li>Elasticsearch：作为搜索引擎的核心，提供全文搜索、结构化搜索和推荐等功能。</li>
<li>Elasticsearch JDBC：用于将关系型数据库中的数据同步到 Elasticsearch 中。</li>
<li>Spring Data Elasticsearch：提供与 Elasticsearch 的集成，简化 Elasticsearch 操作。</li>
<li>Logstash：用于日志收集、解析和传输，便于监控和调试。</li>
<li>Kibana：用于可视化展示 Elasticsearch 中的数据，提供强大的数据分析和可视化功能。</li>
<li>Mybatis：作为持久层框架，用于操作关系型数据库。</li>
<li>Redis：作为缓存数据库，提高系统性能。</li>
<li>Swagger：用于 API 文档的管理和展示。</li>
</ul>
<h3 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h3><ul>
<li>Vue.js：作为前端框架，提供响应式设计和组件化开发的能力。</li>
<li>Element UI：作为 Vue.js 的 UI 组件库，提供丰富的界面元素和样式。</li>
<li>Axios：用于发送 HTTP 请求，与后端 API 进行交互。</li>
<li>Vue Router：用于实现前端路由，管理页面跳转。</li>
<li>Vuex：用于管理前端状态，实现组件之间的数据共享和通信。</li>
<li>ECharts：用于数据可视化，展示统计图表。</li>
</ul>
<h2 id="🍰-快速启动"><a href="#🍰-快速启动" class="headerlink" title="🍰 快速启动"></a>🍰 快速启动</h2><Badge text="beta" type="warning"/>

<Badge text="Vdoing主题"/>

<p>拉取代码后， 如何<code>快速运行该项目</code>？</p>
<h3 id="后端-1"><a href="#后端-1" class="headerlink" title="后端"></a>后端</h3><ul>
<li>配置 MySQL、Redis、Elasticsearch 为本机地址：</li>
</ul>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment"># MySQL配置</span><br><span class="hljs-attr">datasource:</span><br>  <span class="hljs-attr">driver-class-name:</span> <span class="hljs-string">com.mysql.cj.jdbc.Driver</span><br>  <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:mysql://localhost:3306/xxx</span><br>  <span class="hljs-attr">username:</span> <span class="hljs-string">xxx</span><br>  <span class="hljs-attr">password:</span> <span class="hljs-string">xxx</span><br></code></pre></td></tr></table></figure>

<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment"># Redis 配置</span><br><span class="hljs-attr">redis:</span><br>  <span class="hljs-attr">database:</span> <span class="hljs-number">0</span><br>  <span class="hljs-attr">host:</span> <span class="hljs-string">localhost</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">6379</span><br>  <span class="hljs-attr">timeout:</span> <span class="hljs-number">5000</span><br>  <span class="hljs-attr">password:</span> <span class="hljs-string">Dw990831</span><br></code></pre></td></tr></table></figure>

<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment"># ES 配置</span><br><span class="hljs-attr">elasticsearch:</span><br>  <span class="hljs-attr">uris:</span> <span class="hljs-string">http://localhost:9200</span><br>  <span class="hljs-attr">username:</span> <span class="hljs-string">root</span><br>  <span class="hljs-attr">password:</span> <span class="hljs-number">123456</span><br></code></pre></td></tr></table></figure>

<h4 id="额外安装"><a href="#额外安装" class="headerlink" title="额外安装"></a>额外安装</h4><ul>
<li>在本地安装 Elasticsearch、Kibana、Logstash</li>
<li>在 <code>ES 的 bin 目录</code>下执行以下命令，启动 ES：</li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">Elasticsearch.bat<br></code></pre></td></tr></table></figure>

<ul>
<li>在 <code>Kibana 的 bin 目录</code>下执行以下命令，启动 Kibana：</li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">Kibana.bat<br></code></pre></td></tr></table></figure>

<ul>
<li>在 <code>Logstash 的 config 目录</code>下新增 .conf 文件，编写配置文件，做好数据映射（以下配置信息可作为参考）</li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># Sample Logstash configuration for receiving</span><br><span class="hljs-comment"># UDP syslog messages over port 514</span><br><br>input &#123;<br>  jdbc &#123;<br>    jdbc_driver_library =&gt; <span class="hljs-string">&quot;D:\softWare\logstash\logstash-7.17.9\config\mysql-connector-java-8.0.29.jar&quot;</span><br>    jdbc_driver_class =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    jdbc_connection_string =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/******&quot;</span><br>    jdbc_user =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    jdbc_password =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    statement =&gt; <span class="hljs-string">&quot;SELECT * from post where updateTime &gt; :sql_last_value and updateTime &lt; now() order by updateTime desc&quot;</span><br>    use_column_value =&gt; <span class="hljs-literal">true</span><br>    tracking_column_type =&gt; <span class="hljs-string">&quot;timestamp&quot;</span><br>    tracking_column =&gt; <span class="hljs-string">&quot;updatetime&quot;</span><br>    schedule =&gt; <span class="hljs-string">&quot;*/5 * * * * *&quot;</span><br>    jdbc_default_timezone =&gt; <span class="hljs-string">&quot;Asia/Shanghai&quot;</span><br>  &#125;<br>&#125;<br><br>filter &#123;<br>  mutate &#123;<br>    rename =&gt; &#123;<br>      <span class="hljs-string">&quot;updatetime&quot;</span> =&gt; <span class="hljs-string">&quot;updateTime&quot;</span><br>      <span class="hljs-string">&quot;userid&quot;</span> =&gt; <span class="hljs-string">&quot;userId&quot;</span><br>      <span class="hljs-string">&quot;createtime&quot;</span> =&gt; <span class="hljs-string">&quot;createTime&quot;</span><br>      <span class="hljs-string">&quot;isdelete&quot;</span> =&gt; <span class="hljs-string">&quot;isDelete&quot;</span><br>    &#125;<br>    remove_field =&gt; [<span class="hljs-string">&quot;thumbnum&quot;</span>, <span class="hljs-string">&quot;favournum&quot;</span>]<br>  &#125;<br>&#125;<br><br>output &#123;<br>  stdout &#123; codec =&gt; rubydebug &#125;<br><br>  elasticsearch &#123;<br>    hosts =&gt; <span class="hljs-string">&quot;127.0.0.1:9200&quot;</span><br>    index =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    document_id =&gt; <span class="hljs-string">&quot;%&#123;id&#125;&quot;</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>在 Logstash 的<code>根目录</code>下执行以下命令，<strong>加载配置文件并启动 Logstash</strong>：</li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">.\bin\logstash.bat -f .\config\myTask.conf<br></code></pre></td></tr></table></figure>

<h3 id="前端-1"><a href="#前端-1" class="headerlink" title="前端"></a>前端</h3><p>::: warning 注意<br>确保本地 <code>Node.js 环境配置</code>完成，版本为 <code>v18.x.x</code>及以上<br>:::</p>
<ul>
<li>根据<code>后端接口文档</code>，一键生成前端 HTTP 请求接口：</li>
</ul>
<blockquote>
<p>🍖 官方文档：<a target="_blank" rel="noopener" href="https://github.com/ferdikoomen/openapi-typescript-codegen">ferdikoomen/openapi-typescript-codegen (github.com)</a></p>
</blockquote>
<p>安装：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install openapi-typescript-codegen --save-dev<br></code></pre></td></tr></table></figure>

<p>执行命令生成代码：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">openapi --input http://localhost:8104/api/v2/api-docs?group=memory-search --output ./generated --client axios<br></code></pre></td></tr></table></figure>

<ul>
<li>执行成功后，在 OpenAPI.ts 文件下，修改请求的后端地址：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title class_">OpenAPI</span>: <span class="hljs-title class_">OpenAPIConfig</span> = &#123;<br>  <span class="hljs-attr">BASE</span>: <span class="hljs-string">&quot;http://localhost:8104&quot;</span>,<br>  <span class="hljs-attr">VERSION</span>: <span class="hljs-string">&quot;1.0&quot;</span>,<br>  <span class="hljs-attr">WITH_CREDENTIALS</span>: <span class="hljs-literal">true</span>,<br>  <span class="hljs-attr">CREDENTIALS</span>: <span class="hljs-string">&quot;include&quot;</span>,<br>  <span class="hljs-attr">TOKEN</span>: <span class="hljs-literal">undefined</span>,<br>  <span class="hljs-attr">USERNAME</span>: <span class="hljs-literal">undefined</span>,<br>  <span class="hljs-attr">PASSWORD</span>: <span class="hljs-literal">undefined</span>,<br>  <span class="hljs-attr">HEADERS</span>: <span class="hljs-literal">undefined</span>,<br>  <span class="hljs-attr">ENCODE_PATH</span>: <span class="hljs-literal">undefined</span>,<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>执行以下命令，<code>一键启动</code>前端项目：</li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm run serve<br></code></pre></td></tr></table></figure>

<h2 id="🥩-持续优化"><a href="#🥩-持续优化" class="headerlink" title="🥩 持续优化"></a>🥩 持续优化</h2><p>随着项目的发展和用户需求的增加，我们将持续优化系统性能，提升用户体验。</p>
<p>加强系统的安全性措施，定期进行安全审计和漏洞扫描，确保用户数据的安全。引入微服务架构将项目向容器化部署发展，确保系统的可扩展性和灵活性。</p>
<p>同时将引入持续集成与部署的流程，实现自动化测试和部署上线，降低运维成本。</p>
<h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><h2 id="Day1"><a href="#Day1" class="headerlink" title="Day1"></a>Day1</h2><ul>
<li>前端项目初始化 ✔</li>
<li>后端项目初始化 ✔</li>
<li>前端封装全局Axios、Index页面设计 ✔</li>
<li>实现用户改变页面，URL同步改变</li>
<li>前后端联调成功 ✔</li>
</ul>
<h3 id="记录"><a href="#记录" class="headerlink" title="记录"></a>记录</h3><ul>
<li>使用Ant Design Vue快速构建了前端项目，快捷方便，项目构建的实现流程全部记录在《》一文</li>
<li>我使用了一套模板，快速实现了后端项目初始化</li>
<li>前端封装全局Axios、router路由、嵌套路由等基础操作，同样记录在了《》一文中</li>
<li>后端造了几条post评论假数据，前端使用列表组件，简单展示</li>
<li>URL同步页面状态改变，可难死我了：<ul>
<li>使用url记录页面搜索状态，当用户刷新页面时，能够从url中还原之前的搜索状态</li>
<li>实现：用户操作页面，能够改变url地址（搜索内容同步填充在url，切换tab页时，也要填充）</li>
<li>url改变，去改变对应页面状态</li>
</ul>
</li>
</ul>
<h2 id="Day2"><a href="#Day2" class="headerlink" title="Day2"></a>Day2</h2><ul>
<li><h5 id="后端获取到文章、用户、图片信息"><a href="#后端获取到文章、用户、图片信息" class="headerlink" title="后端获取到文章、用户、图片信息"></a>后端获取到文章、用户、图片信息</h5></li>
<li><p>了解数据抓取的几种方式</p>
</li>
</ul>
<h3 id="数据抓取流程"><a href="#数据抓取流程" class="headerlink" title="数据抓取流程"></a>数据抓取流程</h3><ul>
<li>分析数据，怎么抓取</li>
<li>拿到数据后，怎么处理</li>
<li>写入数据库，持久化存储</li>
</ul>
<h3 id="数据抓取的几种方式"><a href="#数据抓取的几种方式" class="headerlink" title="数据抓取的几种方式"></a>数据抓取的几种方式</h3><ul>
<li><p>直接请求数据接口（最方便），可使用HttpClient、Hutool等客户端发送请求</p>
</li>
<li><p>解析<strong>HTML文档/JSON字符串</strong>中的网页内容，获取数据</p>
</li>
<li><p>jsoup解析库，支持发送请求获取HTML文档，解析数据</p>
</li>
</ul>
<h3 id="文章获取"><a href="#文章获取" class="headerlink" title="文章获取"></a>文章获取</h3><ul>
<li><p>去其他网站抓取，持久化存储到数据库中</p>
</li>
<li><h5 id="从互联网上获取数据-gt-爬虫"><a href="#从互联网上获取数据-gt-爬虫" class="headerlink" title="从互联网上获取数据 -&gt; 爬虫"></a>从互联网上获取数据 -&gt; 爬虫</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs java">  <span class="hljs-comment">// 1. 获取数据</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;&#123;\&quot;current\&quot;:1,\&quot;pageSize\&quot;:8,\&quot;sortField\&quot;:\&quot;createTime\&quot;,\&quot;sortOrder\&quot;:\&quot;descend\&quot;,\&quot;category\&quot;:\&quot;文章\&quot;,\&quot;reviewStatus\&quot;:1&#125;&quot;</span>;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;https://www.code-nav.cn/api/post/search/page/vo&quot;</span>;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> HttpRequest<br>                .post(url)<br>                .body(json)<br>                .execute()<br>                .body();<br><span class="hljs-comment">//        System.out.println(result);</span><br>        <span class="hljs-comment">// 2. json 转对象</span><br>        Map&lt;String, Object&gt; map = JSONUtil.toBean(result, Map.class);<br>        <span class="hljs-type">JSONObject</span> <span class="hljs-variable">data</span> <span class="hljs-operator">=</span> (JSONObject) map.get(<span class="hljs-string">&quot;data&quot;</span>);<br>        <span class="hljs-type">JSONArray</span> <span class="hljs-variable">records</span> <span class="hljs-operator">=</span> (JSONArray) data.get(<span class="hljs-string">&quot;records&quot;</span>);<br>        List&lt;Post&gt; articleList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>        <span class="hljs-keyword">for</span> (Object record : records) &#123;<br>            <span class="hljs-type">JSONObject</span> <span class="hljs-variable">tempRecord</span> <span class="hljs-operator">=</span> (JSONObject) record;<br>            <span class="hljs-type">Post</span> <span class="hljs-variable">post</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Post</span>();<br>            post.setTitle(tempRecord.getStr(<span class="hljs-string">&quot;title&quot;</span>));<br>            post.setContent(tempRecord.getStr(<span class="hljs-string">&quot;content&quot;</span>));<br>            <span class="hljs-type">JSONArray</span> <span class="hljs-variable">tags</span> <span class="hljs-operator">=</span> (JSONArray) tempRecord.get(<span class="hljs-string">&quot;tags&quot;</span>);<br>            List&lt;String&gt; tagList = tags.toList(String.class);<br>            post.setTags(JSONUtil.toJsonStr(tagList));<br>            post.setUserId(<span class="hljs-number">1L</span>);<br>            articleList.add(post);<br>        &#125;<br><span class="hljs-comment">//        System.out.println(articleList);</span><br>        <span class="hljs-comment">// 3. 数据入库</span><br>        <span class="hljs-type">boolean</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> postService.saveBatch(articleList);<br>        Assertions.assertTrue(b);<br>    &#125;<br></code></pre></td></tr></table></figure>



<h3 id="用户获取"><a href="#用户获取" class="headerlink" title="用户获取"></a>用户获取</h3><ul>
<li>本地数据，每个网站的用户基本都是自己的，无需从外站获取</li>
</ul>
<h3 id="图片获取"><a href="#图片获取" class="headerlink" title="图片获取"></a>图片获取</h3><ul>
<li>实时抓取，这部分数据不存放在本地数据库，直接从别人的接口（网站/数据库）中获取</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs java">  <span class="hljs-type">long</span> <span class="hljs-variable">current</span> <span class="hljs-operator">=</span> currentPage - <span class="hljs-number">1</span>;<br>        <span class="hljs-comment">// 非空条件，转码</span><br>        <span class="hljs-keyword">if</span> (StringUtils.isNotBlank(searchText)) &#123;<br>            searchText = URLEncoder.encode(searchText, <span class="hljs-string">&quot;UTF-8&quot;</span>);<br>        &#125;<br><br>        <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> String.format(<span class="hljs-string">&quot;https://cn.bing.com/images/search?q=%s&amp;first=%s&quot;</span>, searchText, current);<br>        <span class="hljs-type">Document</span> <span class="hljs-variable">doc</span> <span class="hljs-operator">=</span> Jsoup.connect(url).get();<br>        <span class="hljs-type">Elements</span> <span class="hljs-variable">elements</span> <span class="hljs-operator">=</span> doc.select(<span class="hljs-string">&quot;.iuscp.isv&quot;</span>);<br>        List&lt;Picture&gt; pictureList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>        <span class="hljs-keyword">for</span> (Element element : elements) &#123;<br>            <span class="hljs-comment">// 取图片地址（murl）</span><br>            <span class="hljs-type">String</span> <span class="hljs-variable">m</span> <span class="hljs-operator">=</span> element.select(<span class="hljs-string">&quot;.iusc&quot;</span>).get(<span class="hljs-number">0</span>).attr(<span class="hljs-string">&quot;m&quot;</span>);<br>            Map&lt;String, Object&gt; map = JSONUtil.toBean(m, Map.class);<br>            <span class="hljs-type">String</span> <span class="hljs-variable">murl</span> <span class="hljs-operator">=</span> (String) map.get(<span class="hljs-string">&quot;murl&quot;</span>);<br><span class="hljs-comment">//            System.out.println(murl);</span><br>            <span class="hljs-comment">// 取标题</span><br>            <span class="hljs-type">String</span> <span class="hljs-variable">title</span> <span class="hljs-operator">=</span> element.select(<span class="hljs-string">&quot;.inflnk&quot;</span>).get(<span class="hljs-number">0</span>).attr(<span class="hljs-string">&quot;aria-label&quot;</span>);<br><span class="hljs-comment">//            System.out.println(title);</span><br>            <span class="hljs-type">Picture</span> <span class="hljs-variable">picture</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Picture</span>();<br>            picture.setTitle(title);<br>            picture.setUrl(murl);<br>            pictureList.add(picture);<br>        &#125;<br><br>        Page&lt;Picture&gt; picturePage = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Page</span>&lt;&gt;(pageSize, currentPage);<br>        picturePage.setRecords(pictureList);<br><br>        <span class="hljs-keyword">return</span> picturePage;<br></code></pre></td></tr></table></figure>



<h2 id="Day3"><a href="#Day3" class="headerlink" title="Day3"></a>Day3</h2><h3 id="现有业务场景分析"><a href="#现有业务场景分析" class="headerlink" title="现有业务场景分析"></a>现有业务场景分析</h3><p>实现了在页面获取文章、用户和图片数据</p>
<p>几种不同的业务场景分析：</p>
<ul>
<li>可以根据<strong>不同的Tab页，发送不同的请求</strong>，当用户点击切换标签页，发送不同的请求</li>
<li>如果是<strong>聚合内容</strong>的网页，可以考虑设计后端统一请求接口，设计一个请求搞定所有搜索查询请求</li>
<li>考虑到业务的扩展性：更多的搜索条件，搜索更多的信息。比如不直接返回数据，但返回数据总数，给予用户反馈</li>
</ul>
<h3 id="聚合搜索"><a href="#聚合搜索" class="headerlink" title="聚合搜索"></a>聚合搜索</h3><ol>
<li>浏览器可能会限制请求数量，可以考虑后端统一接口，一个接口搞定所有查询请求数据（后端可以并发）</li>
<li><strong>设计多个特定的接口，分别接收不同的查询请求</strong>：不同的接口接收的<strong>参数不一致</strong>，增加了前后端沟通的负担，可以考虑用一个接口将请求参数统一，前端只需传入固定的参数，后端负责转换和处理参数，减轻前端压力  -&gt; <strong>比如编程导航的分类请求</strong></li>
</ol>
<h3 id="Java并发"><a href="#Java并发" class="headerlink" title="Java并发"></a>Java并发</h3><ul>
<li>异步执行，提升性能</li>
</ul>
<h3 id="适配器模式"><a href="#适配器模式" class="headerlink" title="适配器模式"></a>适配器模式</h3><ul>
<li><p><a target="_blank" rel="noopener" href="https://juejin.cn/post/6844903541413249032">适配器模式（Adapter Pattern） - 掘金 (juejin.cn)</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://juejin.cn/post/7030820543909593102">设计模式之适配器模式(Adapter Pattern)一 - 掘金 (juejin.cn)</a></p>
</li>
</ul>
<h3 id="注册器模式"><a href="#注册器模式" class="headerlink" title="注册器模式"></a>注册器模式</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@PostConstruct</span><br>  <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doInit</span><span class="hljs-params">()</span> &#123;<br>      System.out.println(<span class="hljs-number">1</span>);<br>      typeDataSourceMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>() &#123;&#123;<br>          put(SearchTypeEnum.POST.getValue(), postDataSource);<br>          put(SearchTypeEnum.USER.getValue(), userDataSource);<br>          put(SearchTypeEnum.PICTURE.getValue(), pictureDataSource);<br>      &#125;&#125;;<br>  &#125;<br></code></pre></td></tr></table></figure>



<h5 id="使用适配器模式，干掉了冗长复杂的-switch"><a href="#使用适配器模式，干掉了冗长复杂的-switch" class="headerlink" title="使用适配器模式，干掉了冗长复杂的 switch"></a>使用适配器模式，干掉了冗长复杂的 switch</h5><h2 id="Day4"><a href="#Day4" class="headerlink" title="Day4"></a>Day4</h2><h4 id="根据页面，选择性查询对应页面的数据"><a href="#根据页面，选择性查询对应页面的数据" class="headerlink" title="根据页面，选择性查询对应页面的数据"></a>根据页面，选择性查询对应页面的数据</h4><p>我们实现前端切换文章、用户和图片的Tab页面时，传入type（页面类型）参数，用来指定搜索<strong>哪个页面的数据</strong>，而非全部搜索</p>
<p>前端传入type（类型）参数，通知后端，根据<strong>类型</strong>搜索<strong>对应页面</strong>的数据</p>
<p>默认type为空，则搜索所有页面数据</p>
<h2 id="Day5"><a href="#Day5" class="headerlink" title="Day5"></a>Day5</h2><ul>
<li>再成功地将ES 和 MySQL数据同步后，终于可以进行下一步了</li>
</ul>
<h3 id="搜索高亮"><a href="#搜索高亮" class="headerlink" title="搜索高亮"></a>搜索高亮</h3><ul>
<li><h5 id="官方文档：-Highlighting-Elasticsearch-Guide-7-17-Elastic"><a href="#官方文档：-Highlighting-Elasticsearch-Guide-7-17-Elastic" class="headerlink" title="官方文档：[Highlighting | Elasticsearch Guide 7.17] | Elastic"></a>官方文档：[Highlighting | Elasticsearch Guide <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/highlighting.html">7.17] | Elastic</a></h5></li>
<li><h5 id="如何使搜索词高亮？ES文档里有现成的demo："><a href="#如何使搜索词高亮？ES文档里有现成的demo：" class="headerlink" title="如何使搜索词高亮？ES文档里有现成的demo："></a>如何使搜索词高亮？ES文档里有现成的demo：</h5></li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">GET</span> <span class="hljs-operator">/</span>_search<br>&#123;<br>  &quot;query&quot;: &#123;<br>    &quot;match&quot;: &#123; &quot;content&quot;: &quot;kimchy&quot; &#125;<br>  &#125;,<br>  &quot;highlight&quot;: &#123;<br>    &quot;fields&quot;: &#123;<br>      &quot;content&quot;: &#123;&#125;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>我们执行搜索，能得到如下标识出来的高亮词：</li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20231001205230841.png" alt="image-20231001205230841"></p>
<h4 id="后端-2"><a href="#后端-2" class="headerlink" title="后端"></a>后端</h4><ul>
<li><h5 id="我们使用-Java客户端，这样编写："><a href="#我们使用-Java客户端，这样编写：" class="headerlink" title="我们使用 Java客户端，这样编写："></a>我们使用 Java客户端，这样编写：</h5></li>
<li><h5 id="使所有字段内匹配的关键字高亮：-（2023-10-01晚）"><a href="#使所有字段内匹配的关键字高亮：-（2023-10-01晚）" class="headerlink" title="使所有字段内匹配的关键字高亮： （2023/10/01晚）"></a>使所有字段内匹配的关键字高亮： <strong>（2023/10/01晚）</strong></h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 搜索关键词高亮</span><br><span class="hljs-type">HighlightBuilder</span> <span class="hljs-variable">highlightBuilder</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HighlightBuilder</span>();<br>highlightBuilder.field(<span class="hljs-string">&quot;*&quot;</span>)<br>        .preTags(<span class="hljs-string">&quot;&lt;font color=&#x27;#eea6b7&#x27;&gt;&quot;</span>)<br>        .postTags(<span class="hljs-string">&quot;&lt;/font&gt;&quot;</span>); <span class="hljs-comment">//所有的字段都高亮</span><br>highlightBuilder.requireFieldMatch(<span class="hljs-literal">false</span>);<span class="hljs-comment">//如果要多个字段高亮,这项要为false</span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="或者指定固定字段内的关键词高亮："><a href="#或者指定固定字段内的关键词高亮：" class="headerlink" title="或者指定固定字段内的关键词高亮："></a>或者指定固定字段内的关键词高亮：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//查询带highlight，标题和内容都带上</span><br>      <span class="hljs-type">HighlightBuilder</span> <span class="hljs-variable">highlightBuilder</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HighlightBuilder</span>()<br>              .field(<span class="hljs-string">&quot;content&quot;</span>)<br>              .requireFieldMatch(<span class="hljs-literal">false</span>)<br>              .preTags(<span class="hljs-string">&quot;&lt;font color=&#x27;#eea6b7&#x27;&gt;&quot;</span>)<br>              .postTags(<span class="hljs-string">&quot;&lt;/font&gt;&quot;</span>);<br>      highlightBuilder.field(<span class="hljs-string">&quot;title&quot;</span>)<br>              .requireFieldMa<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="在构造查询中，配置关键字高亮显示："><a href="#在构造查询中，配置关键字高亮显示：" class="headerlink" title="在构造查询中，配置关键字高亮显示："></a>在构造查询中，配置关键字高亮显示：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 构造查询</span><br>     <span class="hljs-type">NativeSearchQuery</span> <span class="hljs-variable">searchQuery</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NativeSearchQueryBuilder</span>()<br>             .withQuery(boolQueryBuilder)<br>             .withHighlightBuilder(highlightBuilder)<br>             .withPageable(pageRequest)<br>             .withSorts(sortBuilder).build();<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="我们抽象出一个类，存放获取的高亮关键词："><a href="#我们抽象出一个类，存放获取的高亮关键词：" class="headerlink" title="我们抽象出一个类，存放获取的高亮关键词："></a>我们抽象出一个类，存放获取的高亮关键词：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;SearchHit&lt;PostEsDTO&gt;&gt; searchHitList = searchHits.getSearchHits();<br>         <span class="hljs-comment">// 搜索关键词高亮</span><br>         Map&lt;Long, PostEsHighlightData&gt; highlightDataMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;&gt;();<br>         <span class="hljs-keyword">for</span> (SearchHit hit : searchHits.getSearchHits()) &#123;<br>             <span class="hljs-type">PostEsHighlightData</span> <span class="hljs-variable">data</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">PostEsHighlightData</span>();<br>             data.setId(Long.valueOf(hit.getId()));<br>             <span class="hljs-keyword">if</span> (hit.getHighlightFields().get(<span class="hljs-string">&quot;title&quot;</span>) != <span class="hljs-literal">null</span>) &#123;<br>                 <span class="hljs-type">String</span> <span class="hljs-variable">highlightTitle</span> <span class="hljs-operator">=</span> String.valueOf(hit.getHighlightFields().get(<span class="hljs-string">&quot;title&quot;</span>));<br>                 data.setTitle(highlightTitle.substring(<span class="hljs-number">1</span>, highlightTitle.length() - <span class="hljs-number">1</span>));<br>                 System.out.println(data.getTitle());<br>             &#125;<br>             <span class="hljs-keyword">if</span> (hit.getHighlightFields().get(<span class="hljs-string">&quot;content&quot;</span>) != <span class="hljs-literal">null</span>) &#123;<br>                 <span class="hljs-type">String</span> <span class="hljs-variable">highlightContent</span> <span class="hljs-operator">=</span> String.valueOf(hit.getHighlightFields().get(<span class="hljs-string">&quot;content&quot;</span>));<br>                 data.setContent(highlightContent.substring(<span class="hljs-number">1</span>, highlightContent.length() - <span class="hljs-number">1</span>));<br>                 System.out.println(data.getContent());<br>             &#125;<br>             highlightDataMap.put(data.getId(), data);<br>         &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="根据-id-拿到每一个-Post对象，使用高亮关键词替换原文本，返回结果："><a href="#根据-id-拿到每一个-Post对象，使用高亮关键词替换原文本，返回结果：" class="headerlink" title="根据 id 拿到每一个 Post对象，使用高亮关键词替换原文本，返回结果："></a>根据 id 拿到每一个 Post对象，使用高亮关键词替换原文本，返回结果：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// id列表</span><br>       List&lt;Long&gt; postIdList = searchHitList.stream().map(searchHit -&gt; searchHit.getContent().getId())<br>               .collect(Collectors.toList());<br>       <span class="hljs-comment">// 根据id查找数据集</span><br>       List&lt;Post&gt; postList = baseMapper.selectBatchIds(postIdList);<br>       <span class="hljs-keyword">if</span> (postList != <span class="hljs-literal">null</span>) &#123;<br>           Map&lt;Long, List&lt;Post&gt;&gt; idPostMap = postList.stream().collect(Collectors.groupingBy(Post::getId));<br>           postIdList.forEach(postId -&gt; &#123;<br>               <span class="hljs-keyword">if</span> (idPostMap.containsKey(postId)) &#123;<br>                   <span class="hljs-comment">// 搜索关键词高亮替换</span><br>                   <span class="hljs-type">Post</span> <span class="hljs-variable">post</span> <span class="hljs-operator">=</span> idPostMap.get(postId).get(<span class="hljs-number">0</span>);<br>                   <span class="hljs-type">String</span> <span class="hljs-variable">hl_title</span> <span class="hljs-operator">=</span> highlightDataMap.get(postId).getTitle();<br>                   <span class="hljs-type">String</span> <span class="hljs-variable">hl_content</span> <span class="hljs-operator">=</span> highlightDataMap.get(postId).getContent();<br>                   <span class="hljs-keyword">if</span> (hl_title != <span class="hljs-literal">null</span> &amp;&amp; hl_title.trim() != <span class="hljs-string">&quot;&quot;</span>) &#123;<br>                       post.setTitle(hl_title);<br>                   &#125;<br>                   <span class="hljs-keyword">if</span> (hl_content != <span class="hljs-literal">null</span> &amp;&amp; hl_content.trim() != <span class="hljs-string">&quot;&quot;</span>) &#123;<br>                       post.setContent(hl_content);<br>                   &#125;<br>                   resourceList.add(post);<br>               &#125; <span class="hljs-keyword">else</span> &#123;<br>                   <span class="hljs-comment">// 从 es 清空 db 已物理删除的数据</span><br>                   <span class="hljs-type">String</span> <span class="hljs-variable">delete</span> <span class="hljs-operator">=</span> elasticsearchRestTemplate.delete(String.valueOf(postId), PostEsDTO.class);<br>                   log.info(<span class="hljs-string">&quot;delete post &#123;&#125;&quot;</span>, delete);<br>               &#125;<br>           &#125;);<br>       &#125;<br>   &#125;<br><br></code></pre></td></tr></table></figure>

<h4 id="前端-2"><a href="#前端-2" class="headerlink" title="前端"></a>前端</h4><ul>
<li>后端已经将关键词高亮特殊处理了，前端应该做什么呢？</li>
<li>将后端响应的文本信息，放入 <strong>v-html 属性</strong>中，即可<strong>解析出文本的 CSS 样式</strong>：</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;!--标题--&gt;<br>        &lt;template #title&gt;<br>          &lt;a href=&quot;https://www.antdv.com/&quot; v-html=&quot;item.title&quot;&gt;&lt;/a&gt;<br>        &lt;/template&gt;<br>        &lt;!--头像--&gt;<br>        &lt;template #avatar&gt;<br>          &lt;a-avatar src=&quot;https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png&quot;/&gt;<br>        &lt;/template&gt;<br>        &lt;template #description&gt;<br>          &lt;div v-html=&quot;item.content&quot; style=&quot;margin-bottom: 10px&quot;&gt;&lt;/div&gt;<br>        &lt;/template&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li>这就是最终的的实现效果了：</li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20231001210038270.png" alt="image-20231001210038270"></p>
<ul>
<li>这让我想起了前两天刚实现过的<strong>前端解析 Markdown 格式文件</strong>的方法： <strong>（2023/10/01晚）</strong></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">import</span> <span class="hljs-title class_">MarkdownIt</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;markdown-it&#x27;</span>;<br><br><span class="hljs-comment">// Markdown语法</span><br><span class="hljs-keyword">const</span> parsedContent = <span class="hljs-title function_">ref</span>()<br><span class="hljs-keyword">const</span> md = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MarkdownIt</span>();<br><span class="hljs-comment">// 使用Markdown语法接收文章内容</span><br>parsedContent.<span class="hljs-property">value</span> = md.<span class="hljs-title function_">render</span>(articleInfo.<span class="hljs-property">value</span>.<span class="hljs-property">content</span>);<br></code></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;div v-html=&quot;parsedContent&quot;<br>      style=&quot;position: absolute; margin-left: 10px; margin-right: 10px; margin-top: 20px;&quot;&gt;<br> &lt;/div&gt;<br></code></pre></td></tr></table></figure>

<h4 id="搜索高亮语法"><a href="#搜索高亮语法" class="headerlink" title="搜索高亮语法"></a>搜索高亮语法</h4><ul>
<li>搜索高亮语法，就是查询词和指示高亮字段相配合的结果：</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">GET</span> post_v1<span class="hljs-operator">/</span>_search<br>&#123;<br>  &quot;query&quot;: &#123;<br>    &quot;match&quot;: &#123; &quot;author&quot;: &quot;杜甫&quot; &#125;<br>  &#125;,<br>    &quot;highlight&quot;: &#123;<br>    &quot;fields&quot;: &#123;<br>      &quot;author&quot;: &#123;&#125;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203205450161.png" alt="image-20231203205450161"></p>
<h3 id="搜索词建议"><a href="#搜索词建议" class="headerlink" title="搜索词建议"></a>搜索词建议</h3><ul>
<li>距离上次搞搜索建议，已经过去半个多月了 <strong>（2023/12/03晚）</strong></li>
</ul>
<blockquote>
<p>🍖 推荐阅读：[ElasticSearch的搜索建议功能suggest search(completion suggest)_es suggest-CSDN博客](<a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_41489540/article/details/121865225?ops_request_misc=%7B&quot;request_id&quot;:&quot;170159810516800211543981&quot;,&quot;scm&quot;:&quot;20140713.130102334.pc_all.&quot;%7D&amp;request_id=170159810516800211543981&amp;biz_id=0&amp;utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-121865225-null-null.142%5Ev96%5Epc_search_result_base5&amp;utm_term=es">https://blog.csdn.net/qq_41489540/article/details/121865225?ops_request_misc={&quot;request_id&quot;%3A&quot;170159810516800211543981&quot;%2C&quot;scm&quot;%3A&quot;20140713.130102334.pc_all.&quot;}&amp;request_id=170159810516800211543981&amp;biz_id=0&amp;utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-121865225-null-null.142^v96^pc_search_result_base5&amp;utm_term=es</a> suggest 搜索建议&amp;spm=1018.2226.3001.4187)</p>
</blockquote>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">GET</span> post_v3<span class="hljs-operator">/</span>_search<br>&#123;<br>  &quot;query&quot;: &#123;<br>    &quot;match&quot;: &#123; &quot;title&quot;: &quot;明日&quot; &#125;<br>  &#125;,<br>    &quot;highlight&quot;: &#123;<br>    &quot;fields&quot;: &#123;<br>      &quot;title&quot;: &#123;&#125;<br>    &#125;<br>  &#125;,<br>    &quot;suggest&quot;: &#123;<span class="hljs-operator">/</span><span class="hljs-operator">/</span>这个字段是关键字,不能随便起名<br>    &quot;my-suggest&quot;: &#123; <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 这个是自己起的名字<br>      &quot;prefix&quot;: &quot;明&quot;, <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 这个是前缀<br>      &quot;completion&quot;: &#123;<br>        &quot;field&quot;: &quot;title.suggest&quot; <span class="hljs-operator">/</span><span class="hljs-operator">/</span>这个是你自己定义的索引<br>      &#125;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203205643525.png" alt="image-20231203205643525"></p>
<ul>
<li>什么是搜索建议？就是根据索引中的某个字段，使用前缀匹配来预先返回该索引字段中的部分文档，就能实现搜索建议</li>
<li>需要注意的是，要想使某个字段支持搜索建议，该字段的值的类型一定要是 <code>completion</code> 的，这里给些<strong>代码示例</strong>，如下：</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs sql">PUT <span class="hljs-operator">/</span>post_v3<span class="hljs-operator">/</span>_mapping<br>&#123;<br>&quot;mappings&quot;: &#123;<br>	&quot;properties&quot;: &#123;<br>		&quot;title&quot;: &#123;<br>			&quot;type&quot;: &quot;text&quot;,<br>			&quot;analyzer&quot;: &quot;ik_max_word&quot;,<br>			&quot;search_analyzer&quot;: &quot;ik_smart&quot;,<br>            &quot;fields&quot;: &#123;<br>                &quot;keyword&quot;: &#123;<br>                    &quot;type&quot;: &quot;keyword&quot;,<br>                    &quot;ignore_above&quot;: <span class="hljs-number">256</span><br>                &#125;,<br>                &quot;suggest&quot;: &#123;  <br>                    &quot;type&quot;: &quot;completion&quot;, <span class="hljs-operator">/</span><span class="hljs-operator">/</span>类型是completion,就是自动补全<br>                    &quot;analyzer&quot;: &quot;ik_max_word&quot;  <br>                &#125;<br>		 &#125;,<br>		 &quot;content&quot;: &#123;<br>			&quot;type&quot;: &quot;text&quot;,<br>			&quot;analyzer&quot;: &quot;ik_max_word&quot;,<br>			&quot;search_analyzer&quot;: &quot;ik_smart&quot;,<br>			&quot;fields&quot;: &#123;<br>				&quot;keyword&quot;: &#123;<br>				&quot;type&quot;: &quot;completion&quot;,<br>				&quot;ignore_above&quot;: <span class="hljs-number">256</span><br>			&#125;, <br>				&quot;suggest&quot;: &#123;  <br>            		&quot;type&quot;: &quot;completion&quot;, <span class="hljs-operator">/</span><span class="hljs-operator">/</span>类型是completion,就是自动补全<br>           			&quot;analyzer&quot;: &quot;ik_max_word&quot;  <br>         		 &#125;<br>			&#125;<br>		  &#125;,<br>		  &quot;userId&quot;: &#123;<br>			&quot;type&quot;: &quot;keyword&quot;<br>		  &#125;,<br>	&#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs sql">PUT <span class="hljs-operator">/</span>post_v3<span class="hljs-operator">/</span>_doc<span class="hljs-operator">/</span><span class="hljs-number">1</span><br>&#123;<br>&quot;title&quot;: &quot;明日之后&quot;,<br>&quot;content&quot;: &quot;拉扎罗夫，我带你回家！&quot;,<br>&quot;userId&quot;:&quot;1008611&quot;<br>&#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">GET</span> post_v3<span class="hljs-operator">/</span>_search<br>&#123;<br>  &quot;query&quot;: &#123;<br>    &quot;match&quot;: &#123; &quot;title&quot;: &quot;明日&quot; &#125;<br>  &#125;,<br>    &quot;highlight&quot;: &#123;<br>    &quot;fields&quot;: &#123;<br>      &quot;title&quot;: &#123;&#125;<br>    &#125;<br>  &#125;,<br>    &quot;suggest&quot;: &#123; <br>    &quot;my-suggest&quot;: &#123; <br>      &quot;prefix&quot;: &quot;明&quot;, <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 这个是前缀<br>      &quot;completion&quot;: &#123;<br>        &quot;field&quot;: &quot;title.suggest&quot; <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 这个是你自己定义的索引<br>      &#125;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>查询结果就是上面展示的查询结果了，这里推荐阅读这篇博客入门：[ElasticSearch的搜索建议功能suggest search(completion suggest)_es suggest-CSDN博客](<a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_41489540/article/details/121865225?ops_request_misc=%7B&quot;request_id&quot;:&quot;170159810516800211543981&quot;,&quot;scm&quot;:&quot;20140713.130102334.pc_all.&quot;%7D&amp;request_id=170159810516800211543981&amp;biz_id=0&amp;utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-121865225-null-null.142%5Ev96%5Epc_search_result_base5&amp;utm_term=es">https://blog.csdn.net/qq_41489540/article/details/121865225?ops_request_misc={&quot;request_id&quot;%3A&quot;170159810516800211543981&quot;%2C&quot;scm&quot;%3A&quot;20140713.130102334.pc_all.&quot;}&amp;request_id=170159810516800211543981&amp;biz_id=0&amp;utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-121865225-null-null.142^v96^pc_search_result_base5&amp;utm_term=es</a> suggest 搜索建议&amp;spm=1018.2226.3001.4187)</li>
<li>这篇博客最终只能算是入门，还有很多细节没有讲清楚，通过了解学习，我也逐渐熟悉了 ES 的 DSL 语法，包括简单的建立索引结构、子字段的作用、插入文档数据、查询文档、字段高亮搜索、搜索建议等等，慢慢理解掌握吧（2023/12/03晚）</li>
<li>最后放一些 AI 回答结果：<strong>（2023/12/03晚）</strong></li>
</ul>
<blockquote>
<p>是的，只有配置为completion字段的字段才支持搜索建议功能。Completion字段是Elasticsearch中专门用于实现搜索建议功能的字段类型。它可以将输入的前缀映射到某个完整的词，并存储在索引中，以便在搜索时提供建议。</p>
<p>当你在字段上配置了completion类型时，Elasticsearch会为该字段创建一个自动完成的字典，并将输入的前缀映射到字典中的完整词。然后，在搜索时，Elasticsearch会根据用户输入的前缀来查找匹配的建议，并将它们返回给用户。</p>
<p>因此，如果你希望在Elasticsearch索引中使用搜索建议功能，你需要确保相关字段被配置为completion类型，以便支持该功能。</p>
</blockquote>
<blockquote>
<p>在Elasticsearch中，为字段创建子字段主要是为了实现多字段索引和搜索需求。在您的示例中，通过在”title”字段下创建一个名为”suggest”的子字段，并将其类型设置为”completion”，您可以实现以下实际应用场景：</p>
<ol>
<li>搜索建议：当用户在搜索时输入”title”字段中的某个前缀时，Elasticsearch可以基于”suggest”子字段中的自动完成字典提供搜索建议。这些建议可以帮助用户更快地找到他们想要的结果，提高搜索效率。</li>
<li>不同的索引方式：在”title”字段下创建”suggest”子字段，可以让您在同一字段上使用不同的索引方式。例如，您可以将”title”字段的主要部分作为文本进行索引，以支持全文搜索，同时将相同字段的另一个部分作为completion字段进行索引，以支持搜索建议功能。</li>
</ol>
<p>总之，通过在字段下创建子字段，您可以实现更灵活和特定的索引和搜索需求。这在处理多语言数据、进行特定领域的搜索优化或提高用户体验等方面非常有用。</p>
</blockquote>
<h3 id="自动创建索引结构"><a href="#自动创建索引结构" class="headerlink" title="自动创建索引结构"></a>自动创建索引结构</h3><ul>
<li>如果不创建索引结构，而直接插入文档数据，Elasticsearch 底层是会自动创建索引结构的对吧？</li>
</ul>
<blockquote>
<p>是的，Elasticsearch底层会自动创建索引结构。当你向Elasticsearch插入文档数据时，Elasticsearch会根据文档中的字段和类型自动创建相应的索引映射和类型定义。</p>
<p>在Elasticsearch中，自动创建的索引结构的字段类型取决于文档中的字段和数据。Elasticsearch支持多种字段类型，包括文本、数字、日期、布尔类型等。</p>
<p>在默认情况下，如果文档中包含一个字段，且该字段的值是字符串类型，那么Elasticsearch会将该字段的类型设置为”text”类型。此外，如果一个字段的值是一个数字或日期类型，那么Elasticsearch会将该字段的类型设置为相应的数字或日期类型。 </p>
<p>需要注意的是，在某些情况下，如果文档中包含未在映射中定义的字段，Elasticsearch可能会自动创建额外的索引映射和类型定义。这可能会导致额外的开销和资源消耗，因此建议在创建索引之前明确定义映射。</p>
<p>总之，虽然Elasticsearch底层会自动创建索引结构，但在生产环境中，为了更好的性能和可维护性，建议在创建索引之前明确定义映射。</p>
</blockquote>
<h3 id="12月份的优化"><a href="#12月份的优化" class="headerlink" title="12月份的优化"></a>12月份的优化</h3><ul>
<li>创建索引</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs sql">PUT <span class="hljs-operator">/</span>article_v2 <br>&#123;  <br>  &quot;mappings&quot;: &#123;  <br>    &quot;properties&quot;: &#123;  <br>      &quot;title&quot;: &#123;  <br>        &quot;type&quot;: &quot;text&quot;,  <br>        &quot;analyzer&quot;: &quot;ik_max_word&quot;,  <br>        &quot;search_analyzer&quot;: &quot;ik_smart&quot;,  <br>        &quot;fields&quot;: &#123;  <br>          &quot;keyword&quot;: &#123;  <br>            &quot;type&quot;: &quot;keyword&quot;,  <br>            &quot;ignore_above&quot;: <span class="hljs-number">256</span>  <br>          &#125;,  <br>          &quot;suggest&quot;: &#123;    <br>            &quot;type&quot;: &quot;completion&quot;,   <br>            &quot;analyzer&quot;: &quot;ik_max_word&quot;    <br>          &#125;  <br>        &#125;  <br>      &#125;,  <br>      &quot;content&quot;: &#123;  <br>        &quot;type&quot;: &quot;text&quot;,  <br>        &quot;analyzer&quot;: &quot;ik_max_word&quot;,  <br>        &quot;search_analyzer&quot;: &quot;ik_smart&quot;,  <br>        &quot;fields&quot;: &#123;  <br>          &quot;suggest&quot;: &#123;    <br>            &quot;type&quot;: &quot;completion&quot;,   <br>            &quot;analyzer&quot;: &quot;ik_max_word&quot;    <br>          &#125;  <br>        &#125;  <br>      &#125;<br>    &#125;  <br>  &#125;  <br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>插入数据</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs sql">PUT <span class="hljs-operator">/</span>article_v2<span class="hljs-operator">/</span>_doc<span class="hljs-operator">/</span><span class="hljs-number">1</span><br>&#123;<br>&quot;title&quot;: &quot;明日之后&quot;,<br>&quot;content&quot;: &quot;拉扎罗夫，我带你回家！&quot;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>搜索建议</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">GET</span> article_v2<span class="hljs-operator">/</span>_search<br>&#123;<br>  &quot;query&quot;: &#123;<br>    &quot;match&quot;: &#123; &quot;title&quot;: &quot;测试&quot; &#125;<br>  &#125;,<br>    &quot;highlight&quot;: &#123;<br>    &quot;fields&quot;: &#123;<br>      &quot;title&quot;: &#123;&#125;<br>    &#125;<br>  &#125;,<br>    &quot;suggest&quot;: &#123;<span class="hljs-operator">/</span><span class="hljs-operator">/</span>这个字段是关键字,不能随便起名<br>    &quot;my-suggest&quot;: &#123; <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 这个是自己起的名字<br>      &quot;prefix&quot;: &quot;测试&quot;, <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 这个是前缀<br>      &quot;completion&quot;: &#123;<br>        &quot;field&quot;: &quot;title.suggest&quot; <span class="hljs-operator">/</span><span class="hljs-operator">/</span>这个是你自己定义的索引<br>      &#125;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>改变存在的索引结构：</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs sql">PUT <span class="hljs-operator">/</span>article_v3<span class="hljs-operator">/</span>_mapping<br>&#123;  <br>    &quot;properties&quot;: &#123;  <br>    &quot;title&quot;: &#123;  <br>      &quot;type&quot;: &quot;text&quot;,  <br>      &quot;analyzer&quot;: &quot;ik_max_word&quot;,  <br>      &quot;search_analyzer&quot;: &quot;ik_smart&quot;,  <br>      &quot;fields&quot;: &#123;  <br>        &quot;keyword&quot;: &#123;  <br>          &quot;type&quot;: &quot;keyword&quot;,  <br>          &quot;ignore_above&quot;: <span class="hljs-number">256</span>  <br>        &#125;,  <br>        &quot;suggest&quot;: &#123;  <br>          &quot;type&quot;: &quot;completion&quot;,  <br>          &quot;analyzer&quot;: &quot;ik_max_word&quot;  <br>        &#125;  <br>      &#125;  <br>    &#125;,  <br>    &quot;content&quot;: &#123;  <br>      &quot;type&quot;: &quot;binary&quot;,  <br>      &quot;fields&quot;: &#123;  <br>        &quot;suggest&quot;: &#123;  <br>          &quot;type&quot;: &quot;completion&quot;,  <br>          &quot;analyzer&quot;: &quot;ik_max_word&quot;  <br>        &#125;  <br>      &#125;  <br>    &#125;  <br>  &#125;  <br>&#125;<br></code></pre></td></tr></table></figure>



<h2 id="持续优化"><a href="#持续优化" class="headerlink" title="持续优化"></a>持续优化</h2><blockquote>
<h5 id="Memory-聚合搜索平台优化："><a href="#Memory-聚合搜索平台优化：" class="headerlink" title="Memory 聚合搜索平台优化："></a>Memory 聚合搜索平台优化：</h5></blockquote>
<ul>
<li>新增博文搜索：<strong>后端提供博文搜索接口</strong>、<strong>前端提供博文列表展示</strong></li>
<li>完善 Gitee 仓库项目介绍</li>
<li>博文搜索实现：新增博文数据库表 Article、ArticleEsDTO、博文 ES 包装类 ArticleEsDTO、博文高亮字段 ArticleEsHighlightData、博文搜索接口 ArticleDataSource <strong>（2023/11/06晚）</strong></li>
<li>待完成：<strong>ES 博文数据同步</strong>、<strong>ES 博文搜索测试</strong>（ES 博文记录）</li>
</ul>
<h3 id="分词器"><a href="#分词器" class="headerlink" title="分词器"></a>分词器</h3><ul>
<li><h5 id="分词器是干啥用的？指定了分词的规则（2023-09-20午）"><a href="#分词器是干啥用的？指定了分词的规则（2023-09-20午）" class="headerlink" title="分词器是干啥用的？指定了分词的规则（2023/09/20午）"></a>分词器是干啥用的？指定了分词的规则（2023/09/20午）</h5></li>
</ul>
<h4 id="内置分词器"><a href="#内置分词器" class="headerlink" title="内置分词器"></a>内置分词器</h4><ul>
<li><h5 id="https-www-elastic-co-guide-en-elasticsearch-reference-7-17-analysis-analyzers-html"><a href="#https-www-elastic-co-guide-en-elasticsearch-reference-7-17-analysis-analyzers-html" class="headerlink" title="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-analyzers.html"></a><a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-analyzers.html">https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-analyzers.html</a></h5></li>
<li><h5 id="了解一下，丰富的分词规则"><a href="#了解一下，丰富的分词规则" class="headerlink" title="了解一下，丰富的分词规则"></a>了解一下，丰富的分词规则</h5></li>
</ul>
<h4 id="空格分词器"><a href="#空格分词器" class="headerlink" title="空格分词器"></a>空格分词器</h4><ul>
<li>whitespace，按空格分词</li>
</ul>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs fsharp">POST <span class="hljs-keyword">_analyze</span><br>&#123;<br>  <span class="hljs-string">&quot;analyzer&quot;</span><span class="hljs-operator">:</span> <span class="hljs-string">&quot;whitespace&quot;</span>,<br>  <span class="hljs-string">&quot;text&quot;</span><span class="hljs-operator">:</span>     <span class="hljs-string">&quot;The quick brown fox.&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure>

<h4 id="关键词分词器"><a href="#关键词分词器" class="headerlink" title="关键词分词器"></a>关键词分词器</h4><ul>
<li><h5 id="就是不分词，整句话当作一个专业术语"><a href="#就是不分词，整句话当作一个专业术语" class="headerlink" title="就是不分词，整句话当作一个专业术语"></a>就是不分词，整句话当作一个专业术语</h5></li>
</ul>
<h4 id="标准分词规则"><a href="#标准分词规则" class="headerlink" title="标准分词规则"></a>标准分词规则</h4><figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs fsharp">POST <span class="hljs-keyword">_analyze</span><br>&#123;<br>  <span class="hljs-string">&quot;tokenizer&quot;</span><span class="hljs-operator">:</span> <span class="hljs-string">&quot;standard&quot;</span>,<br>  <span class="hljs-string">&quot;filter&quot;</span><span class="hljs-operator">:</span>  [ <span class="hljs-string">&quot;lowercase&quot;</span>, <span class="hljs-string">&quot;asciifolding&quot;</span> ],<br>  <span class="hljs-string">&quot;text&quot;</span><span class="hljs-operator">:</span>      <span class="hljs-string">&quot;Is this déja vu?&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="分词器-analyze-和分词规则-tokenizer-有什么区别呢？"><a href="#分词器-analyze-和分词规则-tokenizer-有什么区别呢？" class="headerlink" title="分词器 analyze 和分词规则 tokenizer 有什么区别呢？"></a>分词器 analyze 和分词规则 tokenizer 有什么区别呢？</h5></li>
</ul>
<figure class="highlight coq"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs coq">在搜索引擎和文本分析领域中，分词器（Analyzer）和分词规则器（Tokenizer）是两个不同的概念。<br><br>分词器（Analyzer）是一种将文本转换为单词（<span class="hljs-keyword">Term</span>）序列的工具。它通常包含多个处理步骤，例如词法分析、去除停用词、小写转换、词干提取等。分词器的作用是将原始的文本输入转换为可供索引和搜索的标记流。例如，在Elasticsearch中，分词器被用于预处理文本数据并将其存储在倒排索引中，以支持全文搜索。<br><br>分词规则器（Tokenizer）是分词器的一个组成部分。它是文本分析的第一个处理步骤，将输入的文本按照指定的规则拆分为单词。常见的分词规则器有基于空格拆分的空格分词器、基于标点符号拆分的标点分词器等。分词规则器负责定义文本拆分的方式，决定了哪些字符会被视为词条的分隔。<br><br>总结来说，分词规则器（Tokenizer）是分词器（Analyzer）的组成部分，用于定义文本的拆分方式；而分词器（Analyzer）则包含多个处理步骤，用于将输入文本转换为标记流。<br></code></pre></td></tr></table></figure>

<ul>
<li><p>了解到这些分词器<strong>对中文不友好</strong>，我们需要下载 <strong>IK 分词器</strong> 插件，对<strong>中文分词更加友好</strong>，内置两种分词器</p>
<ul>
<li><h5 id="ik-smart"><a href="#ik-smart" class="headerlink" title="ik_smart"></a>ik_smart</h5></li>
<li><h5 id="ik-max-word"><a href="#ik-max-word" class="headerlink" title="ik_max_word"></a>ik_max_word</h5></li>
</ul>
</li>
</ul>
<h3 id="IK分词器（ES内置插件）"><a href="#IK分词器（ES内置插件）" class="headerlink" title="IK分词器（ES内置插件）"></a>IK分词器（ES内置插件）</h3><h4 id="下载安装"><a href="#下载安装" class="headerlink" title="下载安装"></a>下载安装</h4><ul>
<li><h5 id="7-·-Releases-·-medcl-elasticsearch-analysis-ik-github-com"><a href="#7-·-Releases-·-medcl-elasticsearch-analysis-ik-github-com" class="headerlink" title="7. · Releases · medcl/elasticsearch-analysis-ik (github.com)"></a><a target="_blank" rel="noopener" href="https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.17.7">7. · Releases · medcl/elasticsearch-analysis-ik (github.com)</a></h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230920115244986.png" alt="image-20230920115244986"></p>
<ul>
<li>下载完成，将压缩包解压在 Elasticsearch 的 <strong>plugins / ik目录下即可</strong> </li>
</ul>
<h4 id="修改版本一致"><a href="#修改版本一致" class="headerlink" title="修改版本一致"></a>修改版本一致</h4><ul>
<li><p>解压完成后，修改plugins / ik目录下的 <strong>plugin-descriptor.properties 文件</strong>，将 ik 版本<strong>修改为与 ES 版本一致</strong></p>
<ul>
<li><h5 id="注意：我使用的-ES-版本为-7-17-13，而-ik-版本为-7-17-7（2023-09-20午）"><a href="#注意：我使用的-ES-版本为-7-17-13，而-ik-版本为-7-17-7（2023-09-20午）" class="headerlink" title="注意：我使用的 ES 版本为 7.17.13，而 ik 版本为 7.17.7（2023/09/20午）"></a>注意：我使用的 ES 版本为 7.17.13，而 ik 版本为 7.17.7（2023/09/20午）</h5></li>
<li><p>可能会由于<strong>版本不兼容</strong>，而造成 ES 启动失败，所以需要更改 ik 版本</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230920115558421.png" alt="image-20230920115558421"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230920113332061.png" alt="image-20230920113332061"></p>
<h4 id="启动ES、Kibana"><a href="#启动ES、Kibana" class="headerlink" title="启动ES、Kibana"></a>启动ES、Kibana</h4><ul>
<li><h5 id="启动成功："><a href="#启动成功：" class="headerlink" title="启动成功："></a>启动成功：</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230920120124225.png" alt="image-20230920120124225"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230920120158428.png" alt="image-20230920120158428"></p>
<h4 id="测试分词效果"><a href="#测试分词效果" class="headerlink" title="测试分词效果"></a>测试分词效果</h4><p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230920120416495.png" alt="image-20230920120416495"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230920120422210.png" alt="image-20230920120422210"></p>
<ul>
<li><h5 id="测试成功，这里也能看出来-ik-smart-和-ik-max-word-这两种不同分词模式的区别了（2023-09-20午）"><a href="#测试成功，这里也能看出来-ik-smart-和-ik-max-word-这两种不同分词模式的区别了（2023-09-20午）" class="headerlink" title="测试成功，这里也能看出来 ik_smart 和 ik_max_word 这两种不同分词模式的区别了（2023/09/20午）"></a>测试成功，这里也能看出来 ik_smart 和 ik_max_word 这两种不同分词模式的区别了（2023/09/20午）</h5><ul>
<li><code>ik_smart</code> 模式是 <strong>IK 分词器的简单模式</strong>，它会对文本进行<strong>较为粗粒度的切分</strong>，主要以将句子切分为一些较短的词语为目标，适用于<strong>快速搜索</strong>和一般文本处理场景。该模式下的分词结果倾向于保留短词</li>
<li><code>ik_max_word</code> 模式是 <strong>IK 分词器的细粒度模式</strong>，它会尽可能多地将<strong>文本切分为更小的词语</strong>，包括一些更细致的切分，如<strong>拆分复合词和词组</strong>等。该模式下的分词结果倾向于将文本切分为更多的词</li>
</ul>
</li>
</ul>
<h2 id="ES-调用方式"><a href="#ES-调用方式" class="headerlink" title="ES 调用方式"></a>ES 调用方式</h2><ul>
<li><h5 id="一般来讲，常见的有三种调用方式："><a href="#一般来讲，常见的有三种调用方式：" class="headerlink" title="一般来讲，常见的有三种调用方式："></a>一般来讲，常见的有三种调用方式：</h5><ul>
<li><p>HTTP Restful 调用</p>
</li>
<li><p>Kibana（Dev tools），本质上还是HTTP Restful 调用</p>
</li>
<li><h5 id="客户端调用：Java客户端、Go客户端"><a href="#客户端调用：Java客户端、Go客户端" class="headerlink" title="客户端调用：Java客户端、Go客户端"></a>客户端调用：Java客户端、Go客户端</h5></li>
</ul>
</li>
</ul>
<h3 id="Java-操作-ES"><a href="#Java-操作-ES" class="headerlink" title="Java 操作 ES"></a>Java 操作 ES</h3><ul>
<li><h5 id="一般来讲，也有三种方式："><a href="#一般来讲，也有三种方式：" class="headerlink" title="一般来讲，也有三种方式："></a>一般来讲，也有三种方式：</h5><ul>
<li><p>ES 官方的API：**[Introduction | Elasticsearch Java API Client <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/introduction.html">7.17] | Elastic</a>**</p>
</li>
<li><p>ES以前的官方API：（已废弃，不建议用）</p>
</li>
<li><h5 id="Spring-Data-Elasticsearch（推荐）：Spring-Data-Elasticsearch-Reference-Documentation"><a href="#Spring-Data-Elasticsearch（推荐）：Spring-Data-Elasticsearch-Reference-Documentation" class="headerlink" title="Spring Data Elasticsearch（推荐）：Spring Data Elasticsearch - Reference Documentation"></a>Spring Data Elasticsearch（推荐）：<a target="_blank" rel="noopener" href="https://docs.spring.io/spring-data/elasticsearch/docs/4.4.10/reference/html/">Spring Data Elasticsearch - Reference Documentation</a></h5></li>
</ul>
</li>
<li><p>Spring-data系列：Spring 提供的操作数据的框架</p>
<ul>
<li>Spring-data-redis：操作redis的一套方法</li>
<li>Spring-data-mongodb：操作mongodb的一套方法</li>
<li>Spring-data-elasticsearch：操作elasticsearch的一套方法</li>
</ul>
</li>
</ul>
<h3 id="ES实现搜索接口"><a href="#ES实现搜索接口" class="headerlink" title="ES实现搜索接口"></a>ES实现搜索接口</h3><h4 id="建立索引"><a href="#建立索引" class="headerlink" title="建立索引"></a>建立索引</h4><ul>
<li><h5 id="在ES中，也存在和-MySQL类似的表结构，这里可以将二者对比一下："><a href="#在ES中，也存在和-MySQL类似的表结构，这里可以将二者对比一下：" class="headerlink" title="在ES中，也存在和 MySQL类似的表结构，这里可以将二者对比一下："></a>在ES中，也存在和 MySQL类似的表结构，这里可以将二者对比一下：</h5></li>
</ul>
<table>
<thead>
<tr>
<th><strong>MySQL</strong></th>
<th align="center"><strong>Elasticsearch</strong></th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody><tr>
<td>Table</td>
<td align="center">Index</td>
<td>索引(index)，就是文档的集合，类似数据库的表(table)</td>
</tr>
<tr>
<td>Row</td>
<td align="center">Document</td>
<td>文档（Document），就是一条条的数据，类似数据库中的行（Row），文档都是JSON格式</td>
</tr>
<tr>
<td>Column</td>
<td align="center">Field</td>
<td>字段（Field），就是JSON文档中的字段，类似数据库中的列（Column）</td>
</tr>
<tr>
<td>Schema</td>
<td align="center">Mapping</td>
<td>Mapping（映射）是索引中文档的约束，例如字段类型约束。类似数据库的表结构（Schema）</td>
</tr>
<tr>
<td>SQL</td>
<td align="center">DSL</td>
<td>DSL是elasticsearch提供的JSON风格的请求语句，用来操作elasticsearch，实现CRUD</td>
</tr>
</tbody></table>
<ul>
<li><h5 id="数据库表："><a href="#数据库表：" class="headerlink" title="数据库表："></a>数据库表：</h5></li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> post<br>(<br>    id         <span class="hljs-type">bigint</span> auto_increment comment <span class="hljs-string">&#x27;id&#x27;</span><br>        <span class="hljs-keyword">primary</span> key,<br>    title      <span class="hljs-type">varchar</span>(<span class="hljs-number">512</span>)                       <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;标题&#x27;</span>,<br>    content    text                               <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;内容&#x27;</span>,<br>    tags       <span class="hljs-type">varchar</span>(<span class="hljs-number">1024</span>)                      <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;标签列表（json 数组）&#x27;</span>,<br>    thumbNum   <span class="hljs-type">int</span>      <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>                 <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;点赞数&#x27;</span>,<br>    favourNum  <span class="hljs-type">int</span>      <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>                 <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;收藏数&#x27;</span>,<br>    userId     <span class="hljs-type">bigint</span>                             <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;创建用户 id&#x27;</span>,<br>    createTime datetime <span class="hljs-keyword">default</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;创建时间&#x27;</span>,<br>    updateTime datetime <span class="hljs-keyword">default</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> <span class="hljs-keyword">on</span> <span class="hljs-keyword">update</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> comment <span class="hljs-string">&#x27;更新时间&#x27;</span>,<br>    isDelete   tinyint  <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>                 <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;是否删除&#x27;</span><br>)<br>    comment <span class="hljs-string">&#x27;帖子&#x27;</span> <span class="hljs-keyword">collate</span> <span class="hljs-operator">=</span> utf8mb4_unicode_ci;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="建立索引语句："><a href="#建立索引语句：" class="headerlink" title="建立索引语句："></a>建立索引语句：</h5></li>
<li><p>ES Mapping：</p>
</li>
<li><p>id（可以不放到字段设置里）</p>
</li>
<li><p>ES 中，尽量存放<strong>需要用户筛选</strong>（搜索）的数据</p>
</li>
<li><p><strong>aliases</strong>：别名（为了后续方便数据迁移）</p>
</li>
<li><p>字段类型是 text，这个字段是可被分词的、可模糊查询的；而如果是 keyword，只能完全匹配、精确查询。</p>
</li>
<li><p><strong>analyzer</strong>（存储时生效的分词器）：用 ik_max_word，拆的更碎、索引更多，更有可能被搜出来</p>
</li>
<li><p><strong>search_analyzer</strong>（查询时生效的分词器）：用 ik_smart，更偏向于用户想搜的分词</p>
</li>
<li><p>如果想要让 text 类型的分词字段也<strong>支持精确查询</strong>，可以创建 keyword 类型的子字段：</p>
</li>
</ul>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs css">POST post_v1<br>&#123;<br>&quot;aliases&quot;: &#123;<br>&quot;post&quot;: &#123;&#125;<br>&#125;,<br>&quot;mappings&quot;: &#123;<br>&quot;properties&quot;: &#123;<br>&quot;title&quot;: &#123;<br>&quot;type&quot;: <span class="hljs-string">&quot;text&quot;</span>,<br><span class="hljs-string">&quot;analyzer&quot;</span>: <span class="hljs-string">&quot;ik_max_word&quot;</span>,<br><span class="hljs-string">&quot;search_analyzer&quot;</span>: <span class="hljs-string">&quot;ik_smart&quot;</span>,<br><span class="hljs-string">&quot;fields&quot;</span>: &#123;<br>&quot;keyword&quot;: &#123;<br>&quot;type&quot;: <span class="hljs-string">&quot;keyword&quot;</span>,<br><span class="hljs-string">&quot;ignore_above&quot;</span>: <span class="hljs-number">256</span><br>&#125;<br>&#125;<br>&#125;,<br>&quot;<span class="hljs-attribute">content</span>&quot;: &#123;<br>&quot;type&quot;: <span class="hljs-string">&quot;text&quot;</span>,<br><span class="hljs-string">&quot;analyzer&quot;</span>: <span class="hljs-string">&quot;ik_max_word&quot;</span>,<br><span class="hljs-string">&quot;search_analyzer&quot;</span>: <span class="hljs-string">&quot;ik_smart&quot;</span>,<br><span class="hljs-string">&quot;fields&quot;</span>: &#123;<br>&quot;keyword&quot;: &#123;<br>&quot;type&quot;: <span class="hljs-string">&quot;keyword&quot;</span>,<br><span class="hljs-string">&quot;ignore_above&quot;</span>: <span class="hljs-number">256</span><br>&#125;<br>&#125;<br>&#125;,<br>&quot;tags&quot;: &#123;<br>&quot;type&quot;: <span class="hljs-string">&quot;keyword&quot;</span><br>&#125;,<br>&quot;userId&quot;: &#123;<br>&quot;type&quot;: <span class="hljs-string">&quot;keyword&quot;</span><br>&#125;,<br>&quot;createTime&quot;: &#123;<br>&quot;type&quot;: <span class="hljs-string">&quot;date&quot;</span><br>&#125;,<br>&quot;updateTime&quot;: &#123;<br>&quot;type&quot;: <span class="hljs-string">&quot;date&quot;</span><br>&#125;,<br>&quot;isDelete&quot;: &#123;<br>&quot;type&quot;: <span class="hljs-string">&quot;keyword&quot;</span><br>  &#125;<br>  &#125;<br> &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="SpringBoot项目中引入依赖："><a href="#SpringBoot项目中引入依赖：" class="headerlink" title="SpringBoot项目中引入依赖："></a>SpringBoot项目中引入依赖：</h5></li>
</ul>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>     <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>     <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-data-elasticsearch<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br> <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="开启ES相关配置："><a href="#开启ES相关配置：" class="headerlink" title="开启ES相关配置："></a>开启ES相关配置：</h5></li>
</ul>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">elasticsearch:</span><br>   <span class="hljs-attr">uris:</span> <span class="hljs-string">http://localhost:9200</span><br>   <span class="hljs-attr">username:</span> <span class="hljs-string">root</span><br>   <span class="hljs-attr">password:</span> <span class="hljs-number">123456</span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="索引结构映射："><a href="#索引结构映射：" class="headerlink" title="索引结构映射："></a>索引结构映射：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Document(indexName = &quot;post&quot;)</span><br><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">PostEsDTO</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Serializable</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">DATE_TIME_PATTERN</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;yyyy-MM-dd&#x27;T&#x27;HH:mm:ss.SSS&#x27;Z&#x27;&quot;</span>;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * id</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Id</span><br>    <span class="hljs-keyword">private</span> Long id;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 标题</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> String title;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 内容</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> String content;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 标签列表</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> List&lt;String&gt; tags;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 点赞数</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Integer thumbNum;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 收藏数</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Integer favourNum;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 创建用户 id</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Long userId;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 创建时间</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Field(index = false, store = true, type = FieldType.Date, format = &#123;&#125;, pattern = DATE_TIME_PATTERN)</span><br>    <span class="hljs-keyword">private</span> Date createTime;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 更新时间</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Field(index = false, store = true, type = FieldType.Date, format = &#123;&#125;, pattern = DATE_TIME_PATTERN)</span><br>    <span class="hljs-keyword">private</span> Date updateTime;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 是否删除</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Integer isDelete;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">long</span> <span class="hljs-variable">serialVersionUID</span> <span class="hljs-operator">=</span> <span class="hljs-number">1L</span>;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Gson</span> <span class="hljs-variable">GSON</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 对象转包装类</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> post</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> PostEsDTO <span class="hljs-title function_">objToDto</span><span class="hljs-params">(Post post)</span> &#123;<br>        <span class="hljs-keyword">if</span> (post == <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>        &#125;<br>        <span class="hljs-type">PostEsDTO</span> <span class="hljs-variable">postEsDTO</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">PostEsDTO</span>();<br>        BeanUtils.copyProperties(post, postEsDTO);<br>        <span class="hljs-type">String</span> <span class="hljs-variable">tagsStr</span> <span class="hljs-operator">=</span> post.getTags();<br>        <span class="hljs-keyword">if</span> (StringUtils.isNotBlank(tagsStr)) &#123;<br>            postEsDTO.setTags(GSON.fromJson(tagsStr, <span class="hljs-keyword">new</span> <span class="hljs-title class_">TypeToken</span>&lt;List&lt;String&gt;&gt;() &#123;<br>            &#125;.getType()));<br>        &#125;<br>        <span class="hljs-keyword">return</span> postEsDTO;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 包装类转对象</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> postEsDTO</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Post <span class="hljs-title function_">dtoToObj</span><span class="hljs-params">(PostEsDTO postEsDTO)</span> &#123;<br>        <span class="hljs-keyword">if</span> (postEsDTO == <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>        &#125;<br>        <span class="hljs-type">Post</span> <span class="hljs-variable">post</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Post</span>();<br>        BeanUtils.copyProperties(postEsDTO, post);<br>        List&lt;String&gt; tagList = postEsDTO.getTags();<br>        <span class="hljs-keyword">if</span> (CollectionUtils.isNotEmpty(tagList)) &#123;<br>            post.setTags(GSON.toJson(tagList));<br>        &#125;<br>        <span class="hljs-keyword">return</span> post;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>编写 DAO 层：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">PostEsDao</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">ElasticsearchRepository</span>&lt;PostEsDTO, Long&gt; &#123;<br>    List&lt;PostEsDTO&gt; <span class="hljs-title function_">findByUserId</span><span class="hljs-params">(Long userId)</span>;<br>&#125;<br></code></pre></td></tr></table></figure>

<h4 id="增删改查"><a href="#增删改查" class="headerlink" title="增删改查"></a>增删改查</h4><ul>
<li>测试增删改查：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Resource</span><br><span class="hljs-keyword">private</span> PostEsDao postEsDao;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 新增一条文档</span><br><span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testAdd</span><span class="hljs-params">()</span> &#123;<br>     <span class="hljs-type">PostEsDTO</span> <span class="hljs-variable">postEsDTO</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">PostEsDTO</span>();<br>     postEsDTO.setId(<span class="hljs-number">5L</span>);<br>     postEsDTO.setTitle(<span class="hljs-string">&quot;test&quot;</span>);<br>     postEsDTO.setContent(<span class="hljs-string">&quot;test&quot;</span>);<br>     postEsDTO.setTags(Arrays.asList(<span class="hljs-string">&quot;java&quot;</span>, <span class="hljs-string">&quot;python&quot;</span>));<br>     postEsDTO.setThumbNum(<span class="hljs-number">1</span>);<br>     postEsDTO.setFavourNum(<span class="hljs-number">1</span>);<br>     postEsDTO.setUserId(<span class="hljs-number">1L</span>);<br>     postEsDTO.setCreateTime(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>());<br>     postEsDTO.setUpdateTime(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>());<br>     postEsDTO.setIsDelete(<span class="hljs-number">0</span>);<br>     postEsDao.save(postEsDTO);<br>     System.out.println(postEsDTO.getId());<br> &#125;<br><br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 查找文档</span><br><span class="hljs-meta">@Test</span><br>  <span class="hljs-keyword">void</span> <span class="hljs-title function_">testFindById</span><span class="hljs-params">()</span> &#123;<br>      Optional&lt;PostEsDTO&gt; postEsDTO = postEsDao.findById(<span class="hljs-number">1L</span>);<br>      System.out.println(postEsDTO);<br>  &#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 查找所有文档</span><br><span class="hljs-meta">@Test</span><br>  <span class="hljs-keyword">void</span> <span class="hljs-title function_">testSelect</span><span class="hljs-params">()</span> &#123;<br>      System.out.println(postEsDao.count());<br>      Page&lt;PostEsDTO&gt; PostPage = postEsDao.findAll(<br>              PageRequest.of(<span class="hljs-number">0</span>, <span class="hljs-number">5</span>, Sort.by(<span class="hljs-string">&quot;createTime&quot;</span>)));<br>      List&lt;PostEsDTO&gt; postList = PostPage.getContent();<br>      System.out.println(postList);<br>  &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="简单的增、删、改、查测试通过：（2023-09-20晚）"><a href="#简单的增、删、改、查测试通过：（2023-09-20晚）" class="headerlink" title="简单的增、删、改、查测试通过：（2023/09/20晚）"></a>简单的增、删、改、查测试通过：（2023/09/20晚）</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230920172305509.png" alt="image-20230920172305509"></p>
<h4 id="DSL查询"><a href="#DSL查询" class="headerlink" title="DSL查询"></a>DSL查询</h4><ul>
<li><h5 id="参考文档："><a href="#参考文档：" class="headerlink" title="参考文档："></a>参考文档：</h5></li>
<li><h5 id="Query-and-filter-context-Elasticsearch-Guide-7-17-Elastic"><a href="#Query-and-filter-context-Elasticsearch-Guide-7-17-Elastic" class="headerlink" title="[Query and filter context | Elasticsearch Guide 7.17] | Elastic"></a>[Query and filter context | Elasticsearch Guide <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-filter-context.html">7.17] | Elastic</a></h5></li>
<li><h5 id="Boolean-query-Elasticsearch-Guide-7-17-Elastic"><a href="#Boolean-query-Elasticsearch-Guide-7-17-Elastic" class="headerlink" title="[Boolean query | Elasticsearch Guide 7.17] | Elastic"></a>[Boolean query | Elasticsearch Guide <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-bool-query.html">7.17] | Elastic</a></h5></li>
<li><h5 id="详细的DSL查询学习可以看官网学习，待我学成归来，就在此留下我的学习笔记（2023-09-21晚）"><a href="#详细的DSL查询学习可以看官网学习，待我学成归来，就在此留下我的学习笔记（2023-09-21晚）" class="headerlink" title="详细的DSL查询学习可以看官网学习，待我学成归来，就在此留下我的学习笔记（2023/09/21晚）"></a>详细的DSL查询学习可以看官网学习，待我学成归来，就在此留下我的学习笔记（2023/09/21晚）</h5></li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">GET</span> post<span class="hljs-operator">/</span>_search<br>&#123;<br> &quot;query&quot;: &#123;<br> &quot;bool&quot;: &#123; <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 组合条件<br> &quot;must&quot;: [ <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 必须都满足<br> &#123; &quot;match&quot;: &#123; &quot;title&quot;: &quot;鱼皮&quot; &#125;&#125;, <span class="hljs-operator">/</span><span class="hljs-operator">/</span> <span class="hljs-keyword">match</span> 模糊查询<br> &#123; &quot;match&quot;: &#123; &quot;content&quot;: &quot;知识星球&quot; &#125;&#125;<br> ],<br> &quot;filter&quot;: [<br> &#123; &quot;term&quot;: &#123; &quot;status&quot;: &quot;published&quot; &#125;&#125;, <span class="hljs-operator">/</span><span class="hljs-operator">/</span> term 精确查询<br> &#123; &quot;range&quot;: &#123; &quot;publish_date&quot;: &#123; &quot;gte&quot;: &quot;2015-01-01&quot; &#125;&#125;&#125; <span class="hljs-operator">/</span><span class="hljs-operator">/</span> <span class="hljs-keyword">range</span> 范围<br> ]<br> &#125;<br> &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="建议先测试，再翻译为Java客户端操作"><a href="#建议先测试，再翻译为Java客户端操作" class="headerlink" title="建议先测试，再翻译为Java客户端操作"></a>建议先测试，再翻译为Java客户端操作</h5></li>
</ul>
<h2 id="数据同步"><a href="#数据同步" class="headerlink" title="数据同步"></a>数据同步</h2><blockquote>
<p>🤡 推荐阅读：<a target="_blank" rel="noopener" href="https://juejin.cn/post/7248118049583841336?searchId=2023120321485611272C0B2A87CDE2AEB1">4 种 MySQL 同步 ES 方案，yyds！ - 掘金 (juejin.cn)</a></p>
</blockquote>
<ul>
<li><p>一般情况下，如果做<strong>查询搜索</strong>功能，使用 ES 来<strong>模糊搜索</strong> <strong>（2023/09/21晚）</strong></p>
</li>
<li><p>但是数据是存放在数据库 <strong>MySQL</strong> 里 的，所以说我们需要把 <strong>MySQL 中的数据和 ES 进行同步</strong>，保证<strong>数据一致</strong>（以 MySQL 为主）</p>
</li>
<li><h5 id="MySQL-gt-ES-（单向）"><a href="#MySQL-gt-ES-（单向）" class="headerlink" title="MySQL =&gt; ES （单向）"></a>MySQL =&gt; ES （单向）</h5></li>
<li><p>首次安装完 ES，把 MySQL 数据全量同步到 ES 里，写一个单次脚本 4 种方式，全量同步（首次）+ 增量同步（新数据）：</p>
<ul>
<li><p><strong>定时任务</strong>：比如 1 分钟 1 次，找到 MySQL 中过去几分钟内（至少是定时周期的 2 倍）发 生改变的数据，然后更新到 ES。 </p>
<ul>
<li>优点：简单易懂、占用资源少、不用引入第三方中间件 </li>
<li>缺点：有时间差 应用场景：数据短时间内不同步影响不大、或者数据几乎不发生修改 </li>
</ul>
</li>
<li><p><strong>双写</strong>：写数据的时候，必须也去写 ES；更新删除数据库同理。</p>
<ul>
<li>事务：建议先保证  MySQL 写成功</li>
<li>如果 ES 写失败了，可以通过定时任务 + 日志 + 告警进行检测和修复 （补偿）</li>
</ul>
</li>
<li><p><strong>Logstash 数据同步管道</strong>：（一般要配合 kafka 消息队列 + beats 采集器）</p>
</li>
<li><p><strong>Canal 监听 MySQL Binlog</strong>：实时同步 </p>
</li>
</ul>
</li>
</ul>
<h3 id="Logstash"><a href="#Logstash" class="headerlink" title="Logstash"></a>Logstash</h3><ul>
<li><h5 id="Getting-Started-with-Logstash-Logstash-Reference-7-17-Elastic（2023-09-22晚）"><a href="#Getting-Started-with-Logstash-Logstash-Reference-7-17-Elastic（2023-09-22晚）" class="headerlink" title="[Getting Started with Logstash | Logstash Reference 7.17] | Elastic（2023/09/22晚）"></a>[Getting Started with Logstash | Logstash Reference <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/logstash/7.17/getting-started-with-logstash.html">7.17] | Elastic</a>（2023/09/22晚）</h5></li>
<li><h5 id="Running-Logstash-on-Windows-Logstash-Reference-7-17-Elastic"><a href="#Running-Logstash-on-Windows-Logstash-Reference-7-17-Elastic" class="headerlink" title="[Running Logstash on Windows | Logstash Reference 7.17] | Elastic"></a>[Running Logstash on Windows | Logstash Reference <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/logstash/7.17/running-logstash-windows.html">7.17] | Elastic</a></h5></li>
<li><p><strong>传输</strong> 和 <strong>处理 数据</strong>的管道</p>
<ul>
<li>好处：用起来方便，插件多 </li>
<li>缺点：成本更大、一般要配合其他组件使用（比如 kafka）</li>
</ul>
</li>
<li><p>本质上就是把<strong>编程式同步改为配置式同步</strong>，更加方便快捷</p>
</li>
</ul>
<h4 id="下载安装-1"><a href="#下载安装-1" class="headerlink" title="下载安装"></a>下载安装</h4><ul>
<li><h5 id="Download-Logstash-Free-Get-Started-Now-Elastic"><a href="#Download-Logstash-Free-Get-Started-Now-Elastic" class="headerlink" title="Download Logstash Free | Get Started Now | Elastic"></a><a target="_blank" rel="noopener" href="https://www.elastic.co/cn/downloads/logstash">Download Logstash Free | Get Started Now | Elastic</a></h5></li>
</ul>
<h4 id="demo测试"><a href="#demo测试" class="headerlink" title="demo测试"></a>demo测试</h4><ul>
<li><h5 id="Stashing-Your-First-Event-Logstash-Reference-7-17-Elastic"><a href="#Stashing-Your-First-Event-Logstash-Reference-7-17-Elastic" class="headerlink" title="[Stashing Your First Event | Logstash Reference 7.17] | Elastic"></a>[Stashing Your First Event | Logstash Reference <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/logstash/7.17/first-event.html">7.17] | Elastic</a></h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922205535009.png" alt="image-20230922205535009"></p>
<ul>
<li><h5 id="我们根据官网指引，可以找到这么一段测试代码："><a href="#我们根据官网指引，可以找到这么一段测试代码：" class="headerlink" title="我们根据官网指引，可以找到这么一段测试代码："></a>我们根据官网指引，可以找到这么一段测试代码：</h5></li>
</ul>
<figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs dust"><span class="language-xml">logstash.bat -e &quot;input </span><span class="hljs-template-variable">&#123; stdin &#123; &#125;</span><span class="language-xml"> &#125; output </span><span class="hljs-template-variable">&#123; stdout &#123;&#125;</span><span class="language-xml"> &#125;&quot;</span><br></code></pre></td></tr></table></figure>

<ul>
<li><p>在<strong>bin目录下</strong>执行这段代码（可以理解为：指定输入输出<strong>配置均为默认</strong>，<strong>开启 Logstash</strong>）</p>
</li>
<li><h5 id="待启动完成后，随便输入内容，如果在命令行中有返回相同内容，则测试成功"><a href="#待启动完成后，随便输入内容，如果在命令行中有返回相同内容，则测试成功" class="headerlink" title="待启动完成后，随便输入内容，如果在命令行中有返回相同内容，则测试成功"></a>待启动完成后，随便输入内容，如果在命令行中有返回相同内容，则测试成功</h5></li>
<li><p>如图所示：</p>
</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922205958186.png" alt="image-20230922205958186"></p>
<h4 id="自定义配置"><a href="#自定义配置" class="headerlink" title="自定义配置"></a>自定义配置</h4><ul>
<li><h5 id="快速开始：-Running-Logstash-on-Windows-Logstash-Reference-7-17-Elastic（2023-09-22晚）"><a href="#快速开始：-Running-Logstash-on-Windows-Logstash-Reference-7-17-Elastic（2023-09-22晚）" class="headerlink" title="快速开始：[Running Logstash on Windows | Logstash Reference 7.17] | Elastic（2023/09/22晚）"></a>快速开始：[Running Logstash on Windows | Logstash Reference <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/logstash/7.17/running-logstash-windows.html">7.17] | Elastic</a>（2023/09/22晚）</h5></li>
<li><p>在官方文档中，找到这一段简单的示例配置：</p>
</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922210307761.png" alt="image-20230922210307761"></p>
<ul>
<li>将这段配置粘贴进 <strong>config 下</strong>的 <strong>logstash-sample.conf 配置文件（可以保留该原文件，复制一份重命名）</strong>中：</li>
<li>这几行配置是干什么的呢？简单来讲就是<strong>定义了输入和输出</strong>：<strong>监听 UDP，并输出</strong></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922210525309.png" alt="image-20230922210525309"></p>
<ul>
<li>按官方文档的操作来，尝试<strong>加载这个配置文件</strong> 并 <strong>启动 Logstash</strong>：</li>
</ul>
<figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs livescript">.<span class="hljs-string">\bin\logstash.bat</span> -f .<span class="hljs-string">\config\myTask.conf</span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="Running-Logstash-on-Windows-Logstash-Reference-7-17-Elastic-1"><a href="#Running-Logstash-on-Windows-Logstash-Reference-7-17-Elastic-1" class="headerlink" title="[Running Logstash on Windows | Logstash Reference 7.17] | Elastic"></a>[Running Logstash on Windows | Logstash Reference <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/logstash/7.17/running-logstash-windows.html#running-logstash-windows-manual">7.17] | Elastic</a></h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922211016754.png" alt="image-20230922211016754"></p>
<ul>
<li><h5 id="运行这行命令，可以看到-Logstash-成功启动运行了"><a href="#运行这行命令，可以看到-Logstash-成功启动运行了" class="headerlink" title="运行这行命令，可以看到 Logstash 成功启动运行了"></a>运行这行命令，可以看到 Logstash 成功启动运行了</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922211330283.png" alt="image-20230922211330283"></p>
<h4 id="同步MySQL"><a href="#同步MySQL" class="headerlink" title="同步MySQL"></a>同步MySQL</h4><ul>
<li><h5 id="Jdbc-input-plugin-Logstash-Reference-7-17-Elastic"><a href="#Jdbc-input-plugin-Logstash-Reference-7-17-Elastic" class="headerlink" title="[Jdbc input plugin | Logstash Reference 7.17] | Elastic"></a>[Jdbc input plugin | Logstash Reference <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/logstash/7.17/plugins-inputs-jdbc.html">7.17] | Elastic</a></h5></li>
<li><h5 id="在官方文档中，找到这段配置，用来把-input输入与-MySQL数据库中的数据同步（2023-09-22晚）"><a href="#在官方文档中，找到这段配置，用来把-input输入与-MySQL数据库中的数据同步（2023-09-22晚）" class="headerlink" title="在官方文档中，找到这段配置，用来把 input输入与 MySQL数据库中的数据同步（2023/09/22晚）"></a>在官方文档中，找到这段配置，用来把 input输入与 MySQL数据库中的数据同步（2023/09/22晚）</h5></li>
</ul>
<figure class="highlight puppet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs puppet"><span class="hljs-keyword">input</span> &#123;<br>  jdbc &#123;<br>    <span class="hljs-attr">jdbc_driver_library</span> =&gt; <span class="hljs-string">&quot;mysql-connector-java-5.1.36-bin.jar&quot;</span><br>    <span class="hljs-attr">jdbc_driver_class</span> =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    <span class="hljs-attr">jdbc_connection_string</span> =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/mydb&quot;</span><br>    <span class="hljs-attr">jdbc_user</span> =&gt; <span class="hljs-string">&quot;mysql&quot;</span><br>    <span class="hljs-attr">parameters</span> =&gt; &#123; <span class="hljs-string">&quot;favorite_artist&quot;</span> =&gt; <span class="hljs-string">&quot;Beethoven&quot;</span> &#125;<br>    <span class="hljs-keyword">schedule</span> =&gt; <span class="hljs-string">&quot;* * * * *&quot;</span><br>    statement =&gt; <span class="hljs-string">&quot;SELECT * from songs where artist = :favorite_artist&quot;</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><p>这些配置是不是很眼熟？我们简单说明一下：</p>
<ul>
<li><strong>jdbc_driver_library</strong>：就是加载 MySQL 数据库的 jar 包（依赖）</li>
<li>接下来的四行配置不用多说，连接<strong>MySQL的驱动</strong>、<strong>对应数据库</strong>、<strong>用户名</strong>、<strong>密码</strong></li>
<li><strong>statement</strong>：SQL表达式，用来从 MySQL 中获取数据</li>
<li><strong>parameters</strong>：起到动态配置 SQL 语句中的参数的作用</li>
<li><strong>schedule</strong>：Cron表达式，实现定时查询</li>
</ul>
</li>
<li><h5 id="我们按自己实际的的需求，可以简单地修改配置"><a href="#我们按自己实际的的需求，可以简单地修改配置" class="headerlink" title="我们按自己实际的的需求，可以简单地修改配置"></a>我们按自己实际的的需求，可以简单地修改配置</h5></li>
<li><p>当然了，如果我们我们现在加载此配置、启动 Logstash，一定会报错，如图所示：</p>
</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922213346119.png" alt="image-20230922213346119"></p>
<ul>
<li><h5 id="原因很简单，就是配置中的-mysql-jar包找不到，我们需要自己配置一个-mysql-jar包，并正确配置它的路径"><a href="#原因很简单，就是配置中的-mysql-jar包找不到，我们需要自己配置一个-mysql-jar包，并正确配置它的路径" class="headerlink" title="原因很简单，就是配置中的 mysql jar包找不到，我们需要自己配置一个 mysql jar包，并正确配置它的路径"></a>原因很简单，就是配置中的 mysql jar包找不到，我们需要自己配置一个 mysql jar包，并正确配置它的路径</h5></li>
<li><h5 id="这里有个技巧：在-IDEA-中找到项目所依赖的-jar-包"><a href="#这里有个技巧：在-IDEA-中找到项目所依赖的-jar-包" class="headerlink" title="这里有个技巧：在 IDEA 中找到项目所依赖的 jar 包"></a>这里有个技巧：在 IDEA 中找到项目所依赖的 jar 包</h5><ul>
<li>如图所示，选择对应的依赖后，可以直接在文件管理器中打开</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922213029171.png" alt="image-20230922213029171"></p>
<ul>
<li>然后直接在文件管理器中复制，粘贴到 Logstash 目录下即可</li>
</ul>
</li>
<li><h5 id="加载配置、启动-Logstash，启动成功了："><a href="#加载配置、启动-Logstash，启动成功了：" class="headerlink" title="加载配置、启动 Logstash，启动成功了："></a>加载配置、启动 Logstash，启动成功了：</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922215626868.png" alt="image-20230922215626868"></p>
<ul>
<li><h5 id="聊聊我在这段配置上踩过的坑吧："><a href="#聊聊我在这段配置上踩过的坑吧：" class="headerlink" title="聊聊我在这段配置上踩过的坑吧："></a>聊聊我在这段配置上踩过的坑吧：</h5><ul>
<li>mysql jar 包路径外层多加了一层双引号</li>
<li>用户名、密码配置错误</li>
<li>SQL 语句中 where 多写了一个</li>
<li>timestamp 写成 timestampe</li>
</ul>
</li>
<li><h5 id="这段配置绝对不能出现任何问题，否则就会出现严重的报错。我的最终配置是这样的："><a href="#这段配置绝对不能出现任何问题，否则就会出现严重的报错。我的最终配置是这样的：" class="headerlink" title="这段配置绝对不能出现任何问题，否则就会出现严重的报错。我的最终配置是这样的："></a>这段配置绝对不能出现任何问题，否则就会出现严重的报错。我的最终配置是这样的：</h5></li>
</ul>
<figure class="highlight puppet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs puppet"><span class="hljs-comment"># Sample Logstash configuration for receiving</span><br><span class="hljs-comment"># UDP syslog messages over port 514</span><br><br><span class="hljs-keyword">input</span> &#123;<br>  jdbc &#123;<br>    <span class="hljs-attr">jdbc_driver_library</span> =&gt; <span class="hljs-string">&quot;D:\softWare\logstash\logstash-7.17.9\config\mysql-connector-java-8.0.29.jar&quot;</span><br>    <span class="hljs-attr">jdbc_driver_class</span> =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    <span class="hljs-attr">jdbc_connection_string</span> =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/memory_search&quot;</span><br>    <span class="hljs-attr">jdbc_user</span> =&gt; <span class="hljs-string">&quot;root&quot;</span><br>    <span class="hljs-attr">jdbc_password</span> =&gt; <span class="hljs-string">&quot;Dw990831&quot;</span><br>    <span class="hljs-attr">statement</span> =&gt; <span class="hljs-string">&quot;SELECT * from post where 1 = 1&quot;</span><br>    <span class="hljs-attr">schedule</span> =&gt; <span class="hljs-string">&quot;*/5 * * * *&quot;</span><br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">output</span> &#123;<br>  stdout &#123; <span class="hljs-attr">codec</span> =&gt; rubydebug &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><p>启动成功后，现在的 Logstash 是每5秒从数据库中同步所有数据（当然这是根据SQL语句来执行的），这数据量可能会很大</p>
<ul>
<li><h5 id="这就是全量同步了，我们不需要同步所有数据，我们可以选择同步最近更新的数据（2023-09-22晚）"><a href="#这就是全量同步了，我们不需要同步所有数据，我们可以选择同步最近更新的数据（2023-09-22晚）" class="headerlink" title="这就是全量同步了，我们不需要同步所有数据，我们可以选择同步最近更新的数据（2023/09/22晚）"></a>这就是全量同步了，我们不需要同步所有数据，我们可以选择同步最近更新的数据（2023/09/22晚）</h5></li>
</ul>
</li>
<li><p>添加如下配置：</p>
</li>
</ul>
<figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-attr">statement</span> =&gt; <span class="hljs-string">&quot;SELECT * from post where updateTime &gt; :sql_last_value&quot;</span><br><span class="hljs-attr">use_column_value</span> =&gt; <span class="hljs-literal">true</span><br><span class="hljs-attr">tracking_column_type</span> =&gt; <span class="hljs-string">&quot;timestamp&quot;</span><br><span class="hljs-attr">tracking_column</span> =&gt; <span class="hljs-string">&quot;updateTime&quot;</span><br><span class="hljs-attr">schedule</span> =&gt; <span class="hljs-string">&quot;*/5 * * * * *&quot;</span><br><span class="hljs-attr">jdbc_default_timezone</span> =&gt; <span class="hljs-string">&quot;Asia/Shanghai&quot;</span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="这段配置就是根据-updateTime-字段的最新值，同步updateTime-大于最新值的数据："><a href="#这段配置就是根据-updateTime-字段的最新值，同步updateTime-大于最新值的数据：" class="headerlink" title="这段配置就是根据 updateTime 字段的最新值，同步updateTime 大于最新值的数据："></a>这段配置就是根据 updateTime 字段的最新值，同步updateTime 大于最新值的数据：</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922224744313.png" alt="image-20230922224744313"></p>
<ul>
<li>所以说，<strong>sql_last_value 指定的是上次查到的数据的最后一行的指定字段</strong>，新的查询就是比较这个<strong>指定字段与sql_last_value的大小</strong></li>
<li>但是经过多次查询发现，这里的 <strong>sql_last_value 始终不变</strong><ul>
<li>我们可以在 data\plugins\inputs\jdbc\logstash_jdbc_last_run 看到 <strong>sql_last_value 指定的数据</strong>，确实没有变化：</li>
</ul>
</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922230142159.png" alt="image-20230922230142159"></p>
<ul>
<li>将 tracking_column =&gt; “updateTime” 的 <strong>updateTime 修改为 updatetime</strong>，<strong>日期同步成功</strong></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922225440159.png" alt="image-20230922225440159"></p>
<ul>
<li><h5 id="更新下数据库中的最新值，再看看效果，确实拿到了数据库中最新修改的值（参照上次修改后的最新值）："><a href="#更新下数据库中的最新值，再看看效果，确实拿到了数据库中最新修改的值（参照上次修改后的最新值）：" class="headerlink" title="更新下数据库中的最新值，再看看效果，确实拿到了数据库中最新修改的值（参照上次修改后的最新值）："></a>更新下数据库中的最新值，再看看效果，确实拿到了数据库中最新修改的值（参照上次修改后的最新值）：</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230922230720711.png" alt="image-20230922230720711"></p>
<h4 id="同步ES"><a href="#同步ES" class="headerlink" title="同步ES"></a>同步ES</h4><ul>
<li><p>调试这么久，Logstash 能够<strong>正常同步 MySQL</strong> 了，接下来就是把同步到 input 的数据，<strong>同步到 ES</strong> 中了<strong>（2023/09/22晚）</strong></p>
</li>
<li><p>直接在官方文档中，找到输出 output 的相关配置：</p>
</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230923124744806.png" alt="image-20230923124744806"></p>
<ul>
<li>跟着官网简单的 demo 学就行，配置过一次就会了，这是我<strong>完成同步 ES 后的配置</strong>：（<strong>部分私密信息已做处理</strong>）</li>
</ul>
<figure class="highlight puppet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs puppet"><span class="hljs-comment"># Sample Logstash configuration for receiving</span><br><span class="hljs-comment"># UDP syslog messages over port 514</span><br><br><span class="hljs-keyword">input</span> &#123;<br>  jdbc &#123;<br>    <span class="hljs-attr">jdbc_driver_library</span> =&gt; <span class="hljs-string">&quot;D:\softWare\logstash\logstash-7.17.9\config\mysql-connector-java-8.0.29.jar&quot;</span><br>    <span class="hljs-attr">jdbc_driver_class</span> =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    <span class="hljs-attr">jdbc_connection_string</span> =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/&quot;</span>******<span class="hljs-string">&quot;&quot;</span><br>    <span class="hljs-attr">jdbc_user</span> =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    <span class="hljs-attr">jdbc_password</span> =&gt; <span class="hljs-string">&quot;&quot;</span>******<span class="hljs-string">&quot;&quot;</span><br>    <span class="hljs-attr">statement</span> =&gt; <span class="hljs-string">&quot;SELECT * from post where updateTime &gt; :sql_last_value&quot;</span><br>    <span class="hljs-attr">use_column_value</span> =&gt; <span class="hljs-keyword">true</span><br>    <span class="hljs-attr">tracking_column_type</span> =&gt; <span class="hljs-string">&quot;timestamp&quot;</span><br>    <span class="hljs-attr">tracking_column</span> =&gt; <span class="hljs-string">&quot;updatetime&quot;</span><br>    <span class="hljs-attr">schedule</span> =&gt; <span class="hljs-string">&quot;*/5 * * * * *&quot;</span><br>    <span class="hljs-attr">jdbc_default_timezone</span> =&gt; <span class="hljs-string">&quot;Asia/Shanghai&quot;</span><br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">output</span> &#123;<br>  stdout &#123; <span class="hljs-attr">codec</span> =&gt; rubydebug &#125;<br><br>  <span class="hljs-keyword">elasticsearch</span> &#123;<br>    <span class="hljs-attr">hosts</span> =&gt; <span class="hljs-string">&quot;127.0.0.1:9200&quot;</span><br>    <span class="hljs-attr">index</span> =&gt; <span class="hljs-string">&quot;post_v1&quot;</span><br>    <span class="hljs-attr">document_id</span> =&gt; <span class="hljs-string">&quot;%&#123;id&#125;&quot;</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="这里简单介绍下这几个配置的作用："><a href="#这里简单介绍下这几个配置的作用：" class="headerlink" title="这里简单介绍下这几个配置的作用："></a>这里简单介绍下这几个配置的作用：</h5><ul>
<li><strong>host</strong>：标识要进行同步的 ES 地址，即指定了：<strong>数据从 MySQL 中取出后，发送到哪</strong></li>
<li><strong>index</strong>：目标索引</li>
<li><strong>document_id</strong>：指定目标索引内，每一个文档的 id，就是<strong>从 SELECT * 中解构出 id 值</strong></li>
<li><strong>data_stream</strong>：特殊的数据格式，我们从数据库中取到的都是<strong>普通类型</strong>，<strong>不需要这行配置</strong></li>
</ul>
</li>
<li><p>其他的目前暂且不需要了解，日后再进一步学习</p>
</li>
<li><h5 id="加载配置，运行-Logstash，可以看到运行成功了，数据库中最新更新的数据也成功同步到了本地的-ES-上了："><a href="#加载配置，运行-Logstash，可以看到运行成功了，数据库中最新更新的数据也成功同步到了本地的-ES-上了：" class="headerlink" title="加载配置，运行 Logstash，可以看到运行成功了，数据库中最新更新的数据也成功同步到了本地的 ES 上了："></a>加载配置，运行 Logstash，可以看到运行成功了，数据库中最新更新的数据也成功同步到了本地的 ES 上了：</h5></li>
</ul>
<h5 id=""><a href="#" class="headerlink" title=""></a><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230923125842246.png" alt="image-20230923125842246"></h5><ul>
<li><h5 id="从同步结果来看，我们还需要解决几个问题："><a href="#从同步结果来看，我们还需要解决几个问题：" class="headerlink" title="从同步结果来看，我们还需要解决几个问题："></a>从同步结果来看，我们还需要解决几个问题：</h5><ul>
<li>排除某些不需要同步的字段</li>
<li>ES 中同步过来的文档数据字段都是全小写，不是驼峰式</li>
<li>查询结果按 updateTime 降序排列，避免重排序，导致多同步了不必要的数据，造成性能浪费</li>
</ul>
</li>
<li><p>解决这三个问题当然很简单：</p>
<ul>
<li><h5 id="首先修改下-SQL-语句：（2023-09-23午）"><a href="#首先修改下-SQL-语句：（2023-09-23午）" class="headerlink" title="首先修改下 SQL 语句：（2023/09/23午）"></a>首先修改下 SQL 语句：（2023/09/23午）</h5></li>
</ul>
<figure class="highlight n1ql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs n1ql">statement =&gt; &quot;<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">from</span> post <span class="hljs-keyword">where</span> updateTime &gt; :sql_last_value <span class="hljs-keyword">and</span> updateTime &lt; now() <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> updateTime <span class="hljs-keyword">desc</span><span class="hljs-string">&quot;</span><br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="再写入如下过滤配置，将对应字段进行驼峰式转换，并排除不需要的字段："><a href="#再写入如下过滤配置，将对应字段进行驼峰式转换，并排除不需要的字段：" class="headerlink" title="再写入如下过滤配置，将对应字段进行驼峰式转换，并排除不需要的字段："></a>再写入如下过滤配置，将对应字段进行驼峰式转换，并排除不需要的字段：</h5></li>
</ul>
<figure class="highlight coq"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs coq">filter &#123;<br>  mutate &#123;<br>    <span class="hljs-built_in">rename</span> =&gt; &#123;<br>      <span class="hljs-string">&quot;updatetime&quot;</span> =&gt; <span class="hljs-string">&quot;updateTime&quot;</span><br>      <span class="hljs-string">&quot;userid&quot;</span> =&gt; <span class="hljs-string">&quot;userId&quot;</span><br>      <span class="hljs-string">&quot;createtime&quot;</span> =&gt; <span class="hljs-string">&quot;createTime&quot;</span><br>      <span class="hljs-string">&quot;isdelete&quot;</span> =&gt; <span class="hljs-string">&quot;isDelete&quot;</span><br>    &#125;<br>    remove_field =&gt; [<span class="hljs-string">&quot;thumbnum&quot;</span>, <span class="hljs-string">&quot;favournum&quot;</span>]<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li>
<li><p>重新进行同步，结果完美，顺利完成：</p>
</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20230923144500259.png" alt="image-20230923144500259"></p>
<ul>
<li><h5 id="最后，Kibana-的数据面板也可以了解下"><a href="#最后，Kibana-的数据面板也可以了解下" class="headerlink" title="最后，Kibana 的数据面板也可以了解下"></a>最后，Kibana 的数据面板也可以了解下</h5></li>
</ul>
<h3 id="Logstash-配置多个输入-输出源（已废弃，参考Ⅱ）"><a href="#Logstash-配置多个输入-输出源（已废弃，参考Ⅱ）" class="headerlink" title="Logstash 配置多个输入 / 输出源（已废弃，参考Ⅱ）"></a>Logstash 配置多个输入 / 输出源（已废弃，参考Ⅱ）</h3><blockquote>
<p>🔥 最近在优化 Memory 聚合搜索平台，尝试实现博文 article 的快速搜索和关键词高亮显示等功能</p>
<h5 id="主要工作如下：（2023-11-07晚）"><a href="#主要工作如下：（2023-11-07晚）" class="headerlink" title="主要工作如下：（2023/11/07晚）"></a>主要工作如下：<strong>（2023/11/07晚）</strong></h5><ul>
<li>新增 article 实体，<strong>表结构</strong>已给出 👇</li>
<li>新增博文的 <strong>ES 包装类</strong>（ArticleEsDao）、<strong>博文查询参数</strong>（ArticleQueryRequest）、<strong>博文高亮字段</strong>（ArticleEsHighlightData）</li>
<li>使用 <strong>Spring Data Elasticsearch</strong> 的 <strong>QueryBuilder 组合条件查询</strong>，实现使用 ES <strong>快速搜索博文</strong>和<strong>关键词高亮显示</strong></li>
<li>新增<strong>博文数据源接口</strong>（ArticleDataSource），供聚合搜索调用</li>
<li>配置 Logstash 实现 MySQL 和 ES <strong>数据同步</strong></li>
</ul>
</blockquote>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-comment">-- 博文建表SQL语句</span><br><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> article<br>(<br>    id          <span class="hljs-type">bigint</span>                                 <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;文章id&#x27;</span>,<br>    title       <span class="hljs-type">varchar</span>(<span class="hljs-number">256</span>)                           <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;文章标题&#x27;</span>,<br>    description <span class="hljs-type">varchar</span>(<span class="hljs-number">256</span>)                           <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;文章摘要&#x27;</span>,<br>    content     <span class="hljs-type">varchar</span>(<span class="hljs-number">2048</span>)                          <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;文章内容&#x27;</span>,<br>    author_id   <span class="hljs-type">bigint</span>                                 <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;创作者&#x27;</span>,<br>    <span class="hljs-keyword">view</span>        <span class="hljs-type">int</span>          <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>                 <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;浏览量&#x27;</span>,<br>    likes       <span class="hljs-type">int</span>          <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>                 <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;点赞量&#x27;</span>,<br>    comments    <span class="hljs-type">varchar</span>(<span class="hljs-number">256</span>) <span class="hljs-keyword">default</span> <span class="hljs-string">&#x27;0&#x27;</span>               <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;评论量&#x27;</span>,<br>    create_time datetime     <span class="hljs-keyword">default</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;创建时间&#x27;</span>,<br>    update_time datetime     <span class="hljs-keyword">default</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> <span class="hljs-keyword">on</span> <span class="hljs-keyword">update</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> comment <span class="hljs-string">&#x27;更新时间&#x27;</span>,<br>    is_delete   tinyint      <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>                 <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;逻辑删除&#x27;</span>,<br>    collects    <span class="hljs-type">int</span>                                    <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;收藏量&#x27;</span>,<br>    article_url <span class="hljs-type">varchar</span>(<span class="hljs-number">2048</span>)                          <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;封面图片&#x27;</span>,<br>    tags        <span class="hljs-type">varchar</span>(<span class="hljs-number">256</span>)                           <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;文章标签&#x27;</span><br>)<br>    comment <span class="hljs-string">&#x27;博文&#x27;</span>;<br></code></pre></td></tr></table></figure>

<h4 id="同步配置"><a href="#同步配置" class="headerlink" title="同步配置"></a>同步配置</h4><ul>
<li>新增 article 相关实体的过程这里先<strong>不细讲</strong>，重点记录：如何实现 MySQL 和 ES <strong>数据同步</strong></li>
<li>在 <strong>Logstash 的 config 目录</strong>下，我们作如下配置：<strong>（2023/11/07晚）</strong></li>
</ul>
<figure class="highlight puppet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><code class="hljs puppet"><span class="hljs-comment"># Sample Logstash configuration for receiving</span><br><span class="hljs-comment"># UDP syslog messages over port 514</span><br><br><span class="hljs-keyword">input</span> &#123;<br>  jdbc &#123;<br>    <span class="hljs-attr">jdbc_driver_library</span> =&gt; <span class="hljs-string">&quot;D:\softWare\logstash\logstash-7.17.9\config\mysql-connector-java-8.0.29.jar&quot;</span><br>    <span class="hljs-attr">jdbc_driver_class</span> =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    <span class="hljs-attr">jdbc_connection_string</span> =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/******&quot;</span><br>    <span class="hljs-attr">jdbc_user</span> =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    <span class="hljs-attr">jdbc_password</span> =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    <span class="hljs-attr">statement</span> =&gt; <span class="hljs-string">&quot;SELECT * from article where update_time &gt; :sql_last_value and update_time &lt; now() order by update_time desc&quot;</span><br>    <span class="hljs-attr">use_column_value</span> =&gt; <span class="hljs-keyword">true</span><br>    <span class="hljs-attr">tracking_column_type</span> =&gt; <span class="hljs-string">&quot;timestamp&quot;</span><br>    <span class="hljs-attr">tracking_column</span> =&gt; <span class="hljs-string">&quot;update_time&quot;</span><br>    <span class="hljs-attr">schedule</span> =&gt; <span class="hljs-string">&quot;*/5 * * * * *&quot;</span><br>    <span class="hljs-attr">jdbc_default_timezone</span> =&gt; <span class="hljs-string">&quot;Asia/Shanghai&quot;</span><br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">input</span> &#123;<br>  jdbc &#123;<br>    <span class="hljs-attr">jdbc_driver_library</span> =&gt; <span class="hljs-string">&quot;D:\softWare\logstash\logstash-7.17.9\config\mysql-connector-java-8.0.29.jar&quot;</span><br>    <span class="hljs-attr">jdbc_driver_class</span> =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    <span class="hljs-attr">jdbc_connection_string</span> =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/******&quot;</span><br>    <span class="hljs-attr">jdbc_user</span> =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    <span class="hljs-attr">jdbc_password</span> =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    <span class="hljs-attr">statement</span> =&gt; <span class="hljs-string">&quot;SELECT * from post where updateTime &gt; :sql_last_value and updateTime &lt; now() order by updateTime desc&quot;</span><br>    <span class="hljs-attr">use_column_value</span> =&gt; <span class="hljs-keyword">true</span><br>    <span class="hljs-attr">tracking_column_type</span> =&gt; <span class="hljs-string">&quot;timestamp&quot;</span><br>    <span class="hljs-attr">tracking_column</span> =&gt; <span class="hljs-string">&quot;updatetime&quot;</span><br>    <span class="hljs-attr">schedule</span> =&gt; <span class="hljs-string">&quot;*/5 * * * * *&quot;</span><br>    <span class="hljs-attr">jdbc_default_timezone</span> =&gt; <span class="hljs-string">&quot;Asia/Shanghai&quot;</span><br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">filter</span> &#123;<br>  mutate &#123;<br>    <span class="hljs-attr">rename</span> =&gt; &#123;<br>      <span class="hljs-string">&quot;updatetime&quot;</span> =&gt; <span class="hljs-string">&quot;updateTime&quot;</span><br>      <span class="hljs-string">&quot;userid&quot;</span> =&gt; <span class="hljs-string">&quot;userId&quot;</span><br>      <span class="hljs-string">&quot;createtime&quot;</span> =&gt; <span class="hljs-string">&quot;createTime&quot;</span><br>      <span class="hljs-string">&quot;isdelete&quot;</span> =&gt; <span class="hljs-string">&quot;isDelete&quot;</span><br>    &#125;<br>    <span class="hljs-keyword">remove_field</span> =&gt; [<span class="hljs-string">&quot;thumbnum&quot;</span>, <span class="hljs-string">&quot;favournum&quot;</span>]<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">output</span> &#123;<br>  stdout &#123; <span class="hljs-attr">codec</span> =&gt; rubydebug &#125;<br><br>  <span class="hljs-keyword">elasticsearch</span> &#123;<br>    <span class="hljs-attr">hosts</span> =&gt; <span class="hljs-string">&quot;127.0.0.1:9200&quot;</span><br>    <span class="hljs-attr">index</span> =&gt; <span class="hljs-string">&quot;post_v1&quot;</span><br>    <span class="hljs-attr">document_id</span> =&gt; <span class="hljs-string">&quot;%&#123;id&#125;&quot;</span><br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">output</span> &#123;<br>  stdout &#123; <span class="hljs-attr">codec</span> =&gt; rubydebug &#125;<br><br>  <span class="hljs-keyword">elasticsearch</span> &#123;  <br>    <span class="hljs-attr">hosts</span> =&gt; [<span class="hljs-string">&quot;localhost:9200&quot;</span>]  <br>    <span class="hljs-attr">index</span> =&gt; <span class="hljs-string">&quot;article_v1&quot;</span>  <br>    <span class="hljs-attr">document_id</span> =&gt; <span class="hljs-string">&quot;%&#123;id&#125;&quot;</span>  <br>  &#125;  <br>&#125;<br></code></pre></td></tr></table></figure>

<blockquote>
<h5 id="🥣-我们废话少说，看清楚如上配置-👆"><a href="#🥣-我们废话少说，看清楚如上配置-👆" class="headerlink" title="🥣 我们废话少说，看清楚如上配置 👆"></a>🥣 我们废话少说，看清楚如上配置 👆</h5><ul>
<li><p>如果想要指定多个数据源，就<strong>编写多个 input 块</strong></p>
</li>
<li><p>同样的，如果想要指定多个输出，那么就<strong>编写多个 output 块</strong></p>
</li>
</ul>
</blockquote>
<ul>
<li>比较有趣的是，新增的 <strong>article 实体</strong>的字段是<strong>下划线命名法</strong>，而 <strong>post 实体</strong>的字段却是<strong>驼峰命名法</strong>：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231107223434622.png" alt="image-20231107223434622"></p>
<ul>
<li>这样的属性名肯定是<strong>不规范的</strong>（当然，是因为 <strong>article 是我从 Memory 缘忆交友社区下直接粘贴过来的</strong>）</li>
<li>不过，正好可以比对下<strong>不同命名规范的属性</strong>，在 Logstash 配置中的写法区别了：<strong>（2023/11/07晚）</strong></li>
</ul>
<figure class="highlight puppet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs puppet">-- 下划线命名法<br><span class="hljs-keyword">input</span> &#123;  <br>  jdbc &#123;<br>    <span class="hljs-attr">jdbc_driver_library</span> =&gt; <span class="hljs-string">&quot;D:\softWare\logstash\logstash-7.17.9\config\mysql-connector-java-8.0.29.jar&quot;</span><br>    <span class="hljs-attr">jdbc_driver_class</span> =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    <span class="hljs-attr">jdbc_connection_string</span> =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/******&quot;</span><br>    <span class="hljs-attr">jdbc_user</span> =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    <span class="hljs-attr">jdbc_password</span> =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    <span class="hljs-attr">statement</span> =&gt; <span class="hljs-string">&quot;SELECT * from article where update_time &gt; :sql_last_value and update_time &lt; now() order by update_time desc&quot;</span><br>    <span class="hljs-attr">use_column_value</span> =&gt; <span class="hljs-keyword">true</span><br>    <span class="hljs-attr">tracking_column_type</span> =&gt; <span class="hljs-string">&quot;timestamp&quot;</span><br>    <span class="hljs-attr">tracking_column</span> =&gt; <span class="hljs-string">&quot;update_time&quot;</span><br>    <span class="hljs-attr">schedule</span> =&gt; <span class="hljs-string">&quot;*/5 * * * * *&quot;</span><br>    <span class="hljs-attr">jdbc_default_timezone</span> =&gt; <span class="hljs-string">&quot;Asia/Shanghai&quot;</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight puppet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs puppet">-- 驼峰命名法<br><span class="hljs-keyword">input</span> &#123;<br>  jdbc &#123;<br>    <span class="hljs-attr">jdbc_driver_library</span> =&gt; <span class="hljs-string">&quot;D:\softWare\logstash\logstash-7.17.9\config\mysql-connector-java-8.0.29.jar&quot;</span><br>    <span class="hljs-attr">jdbc_driver_class</span> =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    <span class="hljs-attr">jdbc_connection_string</span> =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/******&quot;</span><br>    <span class="hljs-attr">jdbc_user</span> =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    <span class="hljs-attr">jdbc_password</span> =&gt; <span class="hljs-string">&quot;******&quot;</span><br>    <span class="hljs-attr">statement</span> =&gt; <span class="hljs-string">&quot;SELECT * from post where updateTime &gt; :sql_last_value and updateTime &lt; now() order by updateTime desc&quot;</span><br>    <span class="hljs-attr">use_column_value</span> =&gt; <span class="hljs-keyword">true</span><br>    <span class="hljs-attr">tracking_column_type</span> =&gt; <span class="hljs-string">&quot;timestamp&quot;</span><br>    <span class="hljs-attr">tracking_column</span> =&gt; <span class="hljs-string">&quot;updatetime&quot;</span><br>    <span class="hljs-attr">schedule</span> =&gt; <span class="hljs-string">&quot;*/5 * * * * *&quot;</span><br>    <span class="hljs-attr">jdbc_default_timezone</span> =&gt; <span class="hljs-string">&quot;Asia/Shanghai&quot;</span><br>  &#125;<br></code></pre></td></tr></table></figure>

<h4 id="ES-查询"><a href="#ES-查询" class="headerlink" title="ES 查询"></a>ES 查询</h4><ul>
<li>Logstash 同步配置写完之后，当然要进行测试了，看看数据是否成功从 MySQL 成功同步到了 ES 中</li>
<li>首先<strong>新增 ES 索引</strong>，在 <strong>Kibana 监控面板下</strong>，执行如下 DSL 语句：</li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash">PUT /article_v1  <br>&#123;  <br>  <span class="hljs-string">&quot;settings&quot;</span>: &#123;  <br>    <span class="hljs-string">&quot;number_of_shards&quot;</span>: 3,  <br>    <span class="hljs-string">&quot;number_of_replicas&quot;</span>: 2  <br>  &#125;  <br>&#125;<br></code></pre></td></tr></table></figure>

<blockquote>
<h5 id="🔥-注意："><a href="#🔥-注意：" class="headerlink" title="🔥 注意："></a>🔥 注意：</h5><ul>
<li><h5 id="索引名要跟-Logstash-配置中-output-块下的-index-属性对应："><a href="#索引名要跟-Logstash-配置中-output-块下的-index-属性对应：" class="headerlink" title="索引名要跟 Logstash 配置中 output 块下的 index 属性对应："></a>索引名要跟 Logstash 配置中 output 块下的 <code>index</code> 属性对应：</h5></li>
</ul>
<figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs abnf"><span class="hljs-attribute">index</span> <span class="hljs-operator">=</span>&gt; <span class="hljs-string">&quot;article_v1&quot;</span>  <br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="跟-ArticleEsDao-的-Document-字段对应："><a href="#跟-ArticleEsDao-的-Document-字段对应：" class="headerlink" title="跟 ArticleEsDao 的 Document 字段对应："></a>跟 ArticleEsDao 的 <code>Document</code> 字段对应：</h5></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Document(indexName = &quot;article_v1&quot;)</span><br></code></pre></td></tr></table></figure>
</blockquote>
<ul>
<li>按官方文档的操作来，尝试<strong>加载这个配置文件</strong> 并 <strong>启动 Logstash</strong>：</li>
</ul>
<figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs livescript">.<span class="hljs-string">\bin\logstash.bat</span> -f .<span class="hljs-string">\config\myTask.conf</span><br></code></pre></td></tr></table></figure>

<ul>
<li><strong>随便修改一条记录</strong>（下面的<code>实现 updateTime 字段自动更新</code>一栏中有提到，<strong>数据开始同步</strong> 👇：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231107225828473.png" alt="image-20231107225828473"></p>
<ul>
<li>在 <strong>Kibana 监控面板</strong>下，使用 DSL 语句<strong>执行查询</strong>，效果如下：</li>
</ul>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs routeros"><span class="hljs-built_in">GET</span> article_v1/_search<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231107221155736.png" alt="image-20231107221155736"></p>
<ul>
<li>成功完成 <code>article</code> 实体的<strong>数据同步</strong> <strong>（2023/11/07晚）</strong></li>
</ul>
<h4 id="实现-updateTime-字段自动更新"><a href="#实现-updateTime-字段自动更新" class="headerlink" title="实现 updateTime 字段自动更新"></a>实现 updateTime 字段自动更新</h4><ul>
<li>什么意思呢？我们希望在<strong>修改完数据库表中的记录</strong>后，该条记录<strong>对应的 uodateTime 字段</strong>实现<strong>自动更新</strong></li>
<li>实现方法很简单，在 IDEA 中，直接修改表的 updateTime 字段属性，如下：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231107224250849.png" alt="image-20231107224250849"></p>
<ul>
<li>对应的 <strong><code>DDL</code> 语句</strong>是这样的：</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">updateTime datetime <span class="hljs-keyword">default</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> <span class="hljs-keyword">on</span> <span class="hljs-keyword">update</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> comment <span class="hljs-string">&#x27;更新时间&#x27;</span>,<br></code></pre></td></tr></table></figure>

<ul>
<li>这样，我们<strong>更新记录</strong>后，该记录 updateTime 字段会<strong>自动更新为最近修改时间</strong>：<strong>（2023/11/07晚）</strong></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231107224550821.png" alt="image-20231107224550821"></p>
<blockquote>
<p>SQL，多输入，多输出</p>
</blockquote>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231107213230435.png" alt="image-20231107213230435"></p>
<h3 id="Canal-监控数据库流水"><a href="#Canal-监控数据库流水" class="headerlink" title="Canal 监控数据库流水"></a>Canal 监控数据库流水</h3><blockquote>
<p>🥣 推荐阅读：</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://codingfire.blog.csdn.net/article/details/131420343?spm=1001.2014.3001.5502">Java开发 - Canal的基本用法_canal java-CSDN博客</a></li>
<li><a target="_blank" rel="noopener" href="https://github.com/alibaba/canal/wiki/ClientExample">ClientExample · alibaba/canal Wiki (github.com)</a></li>
</ul>
</blockquote>
<ul>
<li>今天晚上，我们实现了简单的通过 Canal 监控数据库流水，实现了实时监听 MySQL 中的数据变更，并实时同步变更的数据到 Elasticsearch 中，下面简单地介绍下相关流程：<strong>（2023/12/04晚）</strong></li>
</ul>
<h4 id="本地数据库配置"><a href="#本地数据库配置" class="headerlink" title="本地数据库配置"></a>本地数据库配置</h4><ul>
<li>创建新用户：</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">USER</span> canal IDENTIFIED <span class="hljs-keyword">BY</span> <span class="hljs-string">&#x27;canal&#x27;</span>;  <br><span class="hljs-keyword">GRANT</span> <span class="hljs-keyword">SELECT</span>, REPLICATION SLAVE, REPLICATION CLIENT <span class="hljs-keyword">ON</span> <span class="hljs-operator">*</span>.<span class="hljs-operator">*</span> <span class="hljs-keyword">TO</span> <span class="hljs-string">&#x27;canal&#x27;</span>@<span class="hljs-string">&#x27;%&#x27;</span>;<br><span class="hljs-comment">-- GRANT ALL PRIVILEGES ON *.* TO &#x27;canal&#x27;@&#x27;%&#x27; ;</span><br>FLUSH PRIVILEGES;<br></code></pre></td></tr></table></figure>

<ul>
<li>在本地 MySQL 中的 my.ini 文件中做如下配置（将本地的 MySQL 作为一个主节点，开启本地 binlog 生成）：</li>
</ul>
<figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-section">[mysqld]</span><br><span class="hljs-attr">log-bin</span>=mysql-bin <span class="hljs-comment"># 开启 binlog</span><br><span class="hljs-attr">binlog-format</span>=ROW <span class="hljs-comment"># 选择 ROW 模式</span><br><span class="hljs-attr">server_id</span>=<span class="hljs-number">1</span> <span class="hljs-comment"># 配置 MySQL replaction 需要定义，不要和 canal 的 slaveId 重复</span><br></code></pre></td></tr></table></figure>

<h4 id="Canal-的下载安装"><a href="#Canal-的下载安装" class="headerlink" title="Canal 的下载安装"></a>Canal 的下载安装</h4><ul>
<li><a target="_blank" rel="noopener" href="https://github.com/alibaba/canal/wiki/ClientExample">ClientExample · alibaba/canal Wiki (github.com)</a></li>
</ul>
<h4 id="Canal-配置"><a href="#Canal-配置" class="headerlink" title="Canal 配置"></a>Canal 配置</h4><ul>
<li>在 <code>\conf\example\instance.properties</code> 目录下做如下配置：</li>
</ul>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-string">canal.instance.master.address=127.0.0.1:3306</span><br><span class="hljs-string">canal.instance.master.journal.name=mysql-bin.000001</span><br><span class="hljs-string">canal.instance.master.position=</span><br><span class="hljs-string">canal.instance.dbUsername=canal</span><br><span class="hljs-string">canal.instance.dbPassword=canal</span><br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231204204312817.png" alt="image-20231204204312817"></p>
<h4 id="启动-Canal"><a href="#启动-Canal" class="headerlink" title="启动 Canal"></a>启动 Canal</h4><ul>
<li>在 <code>bin</code> 目录下，<strong>输入以下命令启动</strong>：</li>
</ul>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">startup.bat<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231204204656098.png" alt="image-20231204204656098"></p>
<h4 id="开启数据流水监控"><a href="#开启数据流水监控" class="headerlink" title="开启数据流水监控"></a>开启数据流水监控</h4><ul>
<li><p>基本的配置已经做好了，我们需要在项目中实际测验一下监控数据流水（检测数据变更）能否成功<strong>（2023/12/04晚）</strong></p>
</li>
<li><h5 id="导入相关依赖："><a href="#导入相关依赖：" class="headerlink" title="导入相关依赖："></a>导入相关依赖：</h5></li>
</ul>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.otter<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>canal.client<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.1.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure>

<ul>
<li>我们拉取官网提供的 demo 代码，直接启动：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231204203419273.png" alt="image-20231204203419273"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.alibaba.otter.canal.sample;<br><span class="hljs-keyword">import</span> java.net.InetSocketAddress;<br><span class="hljs-keyword">import</span> java.util.List;<br><br><br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.client.CanalConnectors;<br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.client.CanalConnector;<br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.common.utils.AddressUtils;<br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.protocol.Message;<br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.protocol.CanalEntry.Column;<br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.protocol.CanalEntry.Entry;<br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.protocol.CanalEntry.EntryType;<br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.protocol.CanalEntry.EventType;<br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.protocol.CanalEntry.RowChange;<br><span class="hljs-keyword">import</span> com.alibaba.otter.canal.protocol.CanalEntry.RowData;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SimpleCanalClientExample</span> &#123;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String args[])</span> &#123;<br>        <span class="hljs-comment">// 创建链接</span><br>        <span class="hljs-type">CanalConnector</span> <span class="hljs-variable">connector</span> <span class="hljs-operator">=</span> CanalConnectors<br>                .newSingleConnector(<span class="hljs-keyword">new</span> <span class="hljs-title class_">InetSocketAddress</span>(AddressUtils.getHostIp(),<br>                        <span class="hljs-number">11111</span>), <span class="hljs-string">&quot;example&quot;</span>, <span class="hljs-string">&quot;&quot;</span>, <span class="hljs-string">&quot;&quot;</span>);<br>        <span class="hljs-comment">// 获取指定的数据量</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">batchSize</span> <span class="hljs-operator">=</span> <span class="hljs-number">1000</span>;<br>        <span class="hljs-comment">// 空数据计数器</span><br>        <span class="hljs-type">int</span> <span class="hljs-variable">emptyCount</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 建立与 Canal 服务器的连接</span><br>            connector.connect();<br>            <span class="hljs-comment">// 订阅所有数据库的所有表的数据变更事件</span><br>            connector.subscribe(<span class="hljs-string">&quot;.*\\..*&quot;</span>);<br>            <span class="hljs-comment">// 回滚到上次确认的位置，以确保从上次断开连接后开始接收数据</span><br>            connector.rollback();<br>            <span class="hljs-comment">// 进入循环，直到连续收到 120 次空消息为止</span><br>            <span class="hljs-type">int</span> <span class="hljs-variable">totalEmptyCount</span> <span class="hljs-operator">=</span> <span class="hljs-number">120</span>;<br><br>            <span class="hljs-keyword">while</span> (emptyCount &lt; totalEmptyCount) &#123;<br>                <span class="hljs-comment">// 获取消息批次ID和消息大小</span><br>                <span class="hljs-type">Message</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> connector.getWithoutAck(batchSize);<br>                <span class="hljs-type">long</span> <span class="hljs-variable">batchId</span> <span class="hljs-operator">=</span> message.getId();<br>                <span class="hljs-type">int</span> <span class="hljs-variable">size</span> <span class="hljs-operator">=</span> message.getEntries().size();<br><br>                <span class="hljs-comment">// 获取到的消息的批次 ID 为 -1 或者消息的大小为 0，表示没有数据</span><br>                <span class="hljs-keyword">if</span> (batchId == -<span class="hljs-number">1</span> || size == <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-comment">// 将空计数器加一，并打印出当前的空计数值。</span><br>                    emptyCount++;<br>                    System.out.println(<span class="hljs-string">&quot;empty count : &quot;</span> + emptyCount);<br>                    <span class="hljs-comment">// 线程休眠 1 秒钟。</span><br>                    <span class="hljs-keyword">try</span> &#123;<br>                        Thread.sleep(<span class="hljs-number">1000</span>);<br>                    &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                    &#125;<br>                &#125; <span class="hljs-keyword">else</span> &#123;<br>                    <span class="hljs-comment">// 空数据计数器置0</span><br>                    emptyCount = <span class="hljs-number">0</span>;<br>                    System.out.printf(<span class="hljs-string">&quot;message[batchId=%s,size=%s] \n&quot;</span>, batchId, size);<br>                    printEntry(message.getEntries());<br>                &#125;<br><br>                connector.ack(batchId); <span class="hljs-comment">// 提交确认</span><br>                <span class="hljs-comment">// connector.rollback(batchId); // 处理失败, 回滚数据</span><br>            &#125;<br><br>            System.out.println(<span class="hljs-string">&quot;empty too many times, exit&quot;</span>);<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            connector.disconnect();<br>        &#125;<br>    &#125;<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">printEntry</span><span class="hljs-params">(List&lt;Entry&gt; entrys)</span> &#123;<br>    <span class="hljs-keyword">for</span> (Entry entry : entrys) &#123;<br>        <span class="hljs-keyword">if</span> (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) 		&#123;<br>            <span class="hljs-keyword">continue</span>;<br>        &#125;<br><br>        <span class="hljs-type">RowChange</span> <span class="hljs-variable">rowChage</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            rowChage = RowChange.parseFrom(entry.getStoreValue());<br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(<span class="hljs-string">&quot;ERROR ## parser of eromanga-event has an error , data:&quot;</span> + entry.toString(),<br>                                       e);<br>        &#125;<br><br>        <span class="hljs-type">EventType</span> <span class="hljs-variable">eventType</span> <span class="hljs-operator">=</span> rowChage.getEventType();<br>        System.out.println(String.format(<span class="hljs-string">&quot;================&amp;gt; binlog[%s:%s] , name[%s,%s] , eventType : %s&quot;</span>,<br>                                         entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),<br>                                         entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),<br>                                         eventType));<br><br>        <span class="hljs-keyword">for</span> (RowData rowData : rowChage.getRowDatasList()) &#123;<br>            <span class="hljs-keyword">if</span> (eventType == EventType.DELETE) &#123;<br>                printColumn(rowData.getBeforeColumnsList());<br>            &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (eventType == EventType.INSERT) &#123;<br>                printColumn(rowData.getAfterColumnsList());<br>            &#125; <span class="hljs-keyword">else</span> &#123;<br>                System.out.println(<span class="hljs-string">&quot;-------&amp;gt; before&quot;</span>);<br>                printColumn(rowData.getBeforeColumnsList());<br>                System.out.println(<span class="hljs-string">&quot;-------&amp;gt; after&quot;</span>);<br>                printColumn(rowData.getAfterColumnsList());<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">printColumn</span><span class="hljs-params">(List&lt;Column&gt; columns)</span> &#123;<br>    <span class="hljs-keyword">for</span> (Column column : columns) &#123;<br>        System.out.println(column.getName() + <span class="hljs-string">&quot; : &quot;</span> + column.getValue() + <span class="hljs-string">&quot;    update=&quot;</span> + column.getUpdated());<br>    &#125;<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<h4 id="本地数据库修改数据，测试-Canal-监控数据库流水"><a href="#本地数据库修改数据，测试-Canal-监控数据库流水" class="headerlink" title="本地数据库修改数据，测试 Canal 监控数据库流水"></a>本地数据库修改数据，测试 Canal 监控数据库流水</h4><ul>
<li>如下，我们<strong>修改本地数据库中的一条记录</strong>，发现这次数据变更已经被捕捉到并打印出来了</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231204204957589.png" alt="image-20231204204957589"></p>
<p>至此，使用 <strong>Canal 实现实时监控数据变更</strong>就完成了，当然这只是简单测试了一下，日后会着手实现基于该方法<strong>同步 MySQL 中的变更数据到 ES 中</strong> <strong>（2023/12/04晚）</strong></p>
<p>数据处理失败，告诉 Canal 服务器，指定的批次 <code>batchId</code> 的数据没有被成功处理，服务器应该重新传递这批数据。这是一个重试机制，确保数据的完整性和一致性：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">maxRetries</span> <span class="hljs-operator">=</span> <span class="hljs-number">3</span>; <span class="hljs-comment">// 设置最大重试次数</span><br><span class="hljs-keyword">while</span> (maxRetries &gt; <span class="hljs-number">0</span>) &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-comment">// 获取数据并进行处理</span><br>        List&lt;Entry&gt; entries = connector.getWithoutAck(batchSize);<br>        processEntries(entries); <span class="hljs-comment">// 假设这是一个处理数据的方法</span><br><br>        <span class="hljs-comment">// 如果数据处理成功，提交确认</span><br>        connector.ack(batchId);<br>        <span class="hljs-keyword">break</span>; <span class="hljs-comment">// 跳出循环，继续下一次批处理</span><br>    &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>        maxRetries--; <span class="hljs-comment">// 减少剩余重试次数</span><br>        <span class="hljs-keyword">if</span> (maxRetries == <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-comment">// 如果达到最大重试次数，回滚数据</span><br>            connector.rollback(batchId);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>在 deployer / bin 目录下，执行以下命令：<strong>（2024/02/22早）</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">startup.bat<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240222105420902.png" alt="image-20240222105420902"></p>
<p>启动 SimpleCanalClientExample：</p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240222105218658.png" alt="image-20240222105218658"></p>
<h2 id="实现-ES-博文搜索"><a href="#实现-ES-博文搜索" class="headerlink" title="实现 ES 博文搜索"></a>实现 ES 博文搜索</h2><h3 id="严重的问题"><a href="#严重的问题" class="headerlink" title="严重的问题"></a>严重的问题</h3><ul>
<li>着手使用 ES 实现博文快速搜索 + 关键词高亮显示，那<strong>执行步骤很简单</strong>： <strong>（2023/11/08晚）</strong><ul>
<li>后端正确返回博文数据</li>
<li>前端页面展示</li>
</ul>
</li>
<li>由于之前开发过 <code>post</code> 的 <strong>ES 检索</strong> + <strong>关键词高亮显示</strong>，这次开发也是相当自信的</li>
<li>首先使用 DSL 语法，尝试获取 ES 中成功同步的的博文数据，查询成功：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231108224732635.png" alt="image-20231108224732635"></p>
<ul>
<li>结果在执行到 <code>ArticleDataSource</code> 的 <code>searchFromEs()</code> 方法时，却始终查询不到博文数据</li>
<li>经过<strong>一个多小时的仔细排查</strong>，我们终于发现错误出在<strong>这条语句的执行</strong>上：<strong>（2023/11/08晚）</strong></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// id列表</span><br>List&lt;Long&gt; articleIdList = searchHitList.stream().map(searchHit -&gt; searchHit.getContent().getId())<br>                  .collect(Collectors.toList());<br><span class="hljs-comment">// 根据id查找数据集</span><br>List&lt;Article&gt; articleList = baseMapper.selectBatchIds(articleIdList);<br></code></pre></td></tr></table></figure>

<ul>
<li>这条语句的逻辑是<strong>根据搜索条件</strong>在 ES 中查询出符合条件的<strong>索引 id</strong>，再根据索引 id 在本地 MySQL 中查询<strong>对应记录的详细信息</strong></li>
<li>报错详情看下图执行的 SQL 语句：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231108223638259.png" alt="image-20231108223638259"></p>
<ul>
<li><p>这就是我昨天埋下的坑：</p>
<ul>
<li><code>article</code> 和 <code>post</code> 两张表的属性分别是下划线命名法和驼峰命名法（当时顺便展示了<strong>不同命名法下的 logstash 数据同步配置</strong>）</li>
<li>执行 <code>baseMapper.selectBatchIds()</code> 方法时，<strong>MybatisPlus 的字段映射是驼峰映射</strong>（默认的）</li>
<li>这样就找不到对应 article 表中的属性了</li>
</ul>
</li>
<li><p>解决方法很简单，必须<strong>统一数据库中表属性的命名方法</strong>，我们就<strong>统一为驼峰命名法</strong>了，同时也要注意 MP 配置中的的字段映射规则</p>
</li>
<li><p>不过这样的话，Logstash config 下的配置文件中，<strong>article 的数据同步映射配置</strong>需要做小小的修改了（小问题）</p>
</li>
<li><h5 id="有关-MP-配置中的的字段映射规则，明天找时间总结一番"><a href="#有关-MP-配置中的的字段映射规则，明天找时间总结一番" class="headerlink" title="有关 MP 配置中的的字段映射规则，明天找时间总结一番"></a>有关 MP 配置中的的字段映射规则，明天找时间总结一番</h5></li>
</ul>
<figure class="highlight clean"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs clean">org.springframework.jdbc.BadSqlGrammarException: <br>### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column <span class="hljs-string">&#x27;authorId&#x27;</span> <span class="hljs-keyword">in</span> <span class="hljs-string">&#x27;field list&#x27;</span><br>### The error may exist <span class="hljs-keyword">in</span> com/yupi/springbootinit/mapper/ArticleMapper.java (best guess)<br>### The error may involve defaultParameterMap<br>### The error occurred while setting parameters<br>### SQL: SELECT id,title,description,content,authorId,view,likes,comments,createTime,updateTime,isDelete,collects,articleUrl,tags FROM article WHERE id IN (   ?  ,  ?  )  AND isDelete=<span class="hljs-number">0</span><br>### Cause: java.sql.SQLSyntaxErrorException: Unknown column <span class="hljs-string">&#x27;authorId&#x27;</span> <span class="hljs-keyword">in</span> <span class="hljs-string">&#x27;field list&#x27;</span><br>; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Unknown column <span class="hljs-string">&#x27;authorId&#x27;</span> <span class="hljs-keyword">in</span> <span class="hljs-string">&#x27;field list&#x27;</span><br></code></pre></td></tr></table></figure>

<ul>
<li>忙活了一晚上，后端总算能正常查询出 ES 中的数据了，<strong>效果如下</strong>：<strong>（2023/11/08晚）</strong></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231108230233426.png" alt="image-20231108230233426"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231108230244201.png" alt="image-20231108230244201"></p>
<h3 id="前端页面开发"><a href="#前端页面开发" class="headerlink" title="前端页面开发"></a>前端页面开发</h3><ul>
<li><strong>核心代码</strong>展示：</li>
</ul>
<figure class="highlight handlebars"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs handlebars"><span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">a-list-item-meta</span>&gt;</span></span><br><span class="language-xml">         <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">title</span>&gt;</span></span><br><span class="language-xml">           <span class="hljs-comment">&lt;!--&lt;a @click=&quot;goToRead(item.id)&quot;&gt;</span></span><span class="hljs-template-variable">&#123;&#123; <span class="hljs-name">item.title</span> &#125;&#125;</span><span class="language-xml"><span class="hljs-comment">&lt;/a&gt;--&gt;</span></span><br><span class="language-xml">           <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">v-html</span>=<span class="hljs-string">&quot;item.title&quot;</span> <span class="hljs-attr">style</span>=<span class="hljs-string">&quot;margin-bottom: 10px&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span><br><span class="language-xml">         <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span><br><span class="language-xml"></span><br><span class="language-xml">         <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">description</span>&gt;</span></span><br><span class="language-xml">           <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">v-html</span>=<span class="hljs-string">&quot;item.description&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span><br><span class="language-xml">         <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span><br><span class="language-xml">       <span class="hljs-tag">&lt;/<span class="hljs-name">a-list-item-meta</span>&gt;</span></span><br></code></pre></td></tr></table></figure>

<ul>
<li>博文页面默认展示<strong>文章详细信息</strong>：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231109135611922.png" alt="image-20231109135611922"></p>
<ul>
<li>执行搜索后，根据 <code>博文标题（title）</code> 和 <code>文章摘要（description）</code> 快速检索，并实现<strong>关键词高亮</strong>：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231109140530297.png" alt="image-20231109140530297"></p>
<ul>
<li>一个多月前，<strong>Memory 缘忆交友社区</strong>致力于实现的<strong>核心功能</strong>，今天基本实现了 <strong>（2023/11/09午）</strong></li>
<li>在  新增了博文摘要（description）,完成根据摘要的关键词高亮显示：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ArticleEsHighlightData</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * id</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Long id;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 标题</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> String title;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 摘要</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> String description;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 内容</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> String content;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>在执行查询前，指定高亮显示字段</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 查询带highlight，标题和摘要都带上</span><br>       <span class="hljs-type">HighlightBuilder</span> <span class="hljs-variable">highlightBuilder</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HighlightBuilder</span>()<br>               .field(<span class="hljs-string">&quot;description&quot;</span>)<br>               .requireFieldMatch(<span class="hljs-literal">false</span>)<br>               .preTags(<span class="hljs-string">&quot;&lt;font color=&#x27;#eea6b7&#x27;&gt;&quot;</span>)<br>               .postTags(<span class="hljs-string">&quot;&lt;/font&gt;&quot;</span>);<br>       highlightBuilder.field(<span class="hljs-string">&quot;title&quot;</span>)<br>               .requireFieldMatch(<span class="hljs-literal">false</span>)<br>               .preTags(<span class="hljs-string">&quot;&lt;font color=&#x27;#eea6b7&#x27;&gt;&quot;</span>)<br>               .postTags(<span class="hljs-string">&quot;&lt;/font&gt;&quot;</span>);<br></code></pre></td></tr></table></figure>

<ul>
<li>构造查询（过滤 + 排序 + 分页 + 关键字高亮字段）</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 排序</span><br>       SortBuilder&lt;?&gt; sortBuilder = SortBuilders.scoreSort();<br>       <span class="hljs-keyword">if</span> (StringUtils.isNotBlank(sortField)) &#123;<br>           sortBuilder = SortBuilders.fieldSort(sortField);<br>           sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC : SortOrder.DESC);<br>       &#125;<br>       <span class="hljs-comment">// 分页</span><br>       <span class="hljs-type">PageRequest</span> <span class="hljs-variable">pageRequest</span> <span class="hljs-operator">=</span> PageRequest.of((<span class="hljs-type">int</span>) pageNum, (<span class="hljs-type">int</span>) pageSize);<br>       <span class="hljs-comment">// 构造查询</span><br>       <span class="hljs-type">NativeSearchQuery</span> <span class="hljs-variable">searchQuery</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NativeSearchQueryBuilder</span>()<br>               .withQuery(boolQueryBuilder)<br>               .withHighlightBuilder(highlightBuilder)<br>               .withPageable(pageRequest)<br>               .withSorts(sortBuilder).build();<br>       SearchHits&lt;ArticleEsDTO&gt; searchHits = elasticsearchRestTemplate.search(searchQuery, ArticleEsDTO.class);<br></code></pre></td></tr></table></figure>

<ul>
<li>从查询结果中，<strong>获取高亮字段</strong></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 查出结果后，从 db 获取最新动态数据（比如点赞数）</span><br>      <span class="hljs-keyword">if</span> (searchHits.hasSearchHits()) &#123;<br>          List&lt;SearchHit&lt;ArticleEsDTO&gt;&gt; searchHitList = searchHits.getSearchHits();<br>          <span class="hljs-comment">// 搜索关键词高亮</span><br>          Map&lt;Long, ArticleEsHighlightData&gt; highlightDataMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;&gt;();<br>          <span class="hljs-keyword">for</span> (SearchHit hit : searchHits.getSearchHits()) &#123;<br>              <span class="hljs-type">ArticleEsHighlightData</span> <span class="hljs-variable">data</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArticleEsHighlightData</span>();<br>              data.setId(Long.valueOf(hit.getId()));<br>              <span class="hljs-keyword">if</span> (hit.getHighlightFields().get(<span class="hljs-string">&quot;title&quot;</span>) != <span class="hljs-literal">null</span>) &#123;<br>                  <span class="hljs-type">String</span> <span class="hljs-variable">highlightTitle</span> <span class="hljs-operator">=</span> String.valueOf(hit.getHighlightFields().get(<span class="hljs-string">&quot;title&quot;</span>));<br>                  data.setTitle(highlightTitle.substring(<span class="hljs-number">1</span>, highlightTitle.length() - <span class="hljs-number">1</span>));<br>                  System.out.println(data.getTitle());<br>              &#125;<br>              <span class="hljs-keyword">if</span> (hit.getHighlightFields().get(<span class="hljs-string">&quot;description&quot;</span>) != <span class="hljs-literal">null</span>) &#123;<br>                  <span class="hljs-type">String</span> <span class="hljs-variable">highlightContent</span> <span class="hljs-operator">=</span> String.valueOf(hit.getHighlightFields().get(<span class="hljs-string">&quot;description&quot;</span>));<br>                  data.setDescription(highlightContent.substring(<span class="hljs-number">1</span>, highlightContent.length() - <span class="hljs-number">1</span>));<br>                  System.out.println(data.getContent());<br>              &#125;<br>              highlightDataMap.put(data.getId(), data);<br>          &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>使用<strong>高亮字段替代原本</strong>的内容：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">if</span> (idArticleMap.containsKey(articleId)) &#123;<br>                     <span class="hljs-comment">// 搜索关键词高亮替换</span><br>                     <span class="hljs-type">Article</span> <span class="hljs-variable">article</span> <span class="hljs-operator">=</span> idArticleMap.get(articleId).get(<span class="hljs-number">0</span>);<br>                     <span class="hljs-type">String</span> <span class="hljs-variable">hl_title</span> <span class="hljs-operator">=</span> highlightDataMap.get(articleId).getTitle();<br>                     <span class="hljs-type">String</span> <span class="hljs-variable">hl_des</span> <span class="hljs-operator">=</span> highlightDataMap.get(articleId).getDescription();<br>                     <span class="hljs-keyword">if</span> (hl_title != <span class="hljs-literal">null</span> &amp;&amp; hl_title.trim() != <span class="hljs-string">&quot;&quot;</span>) &#123;<br>                         article.setTitle(hl_title);<br>                     &#125;<br>                     <span class="hljs-keyword">if</span> (hl_des != <span class="hljs-literal">null</span> &amp;&amp; hl_des.trim() != <span class="hljs-string">&quot;&quot;</span>) &#123;<br>                         article.setDescription(hl_des);<br>                     &#125;<br>                     resourceList.add(article);<br>                 &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>怎么个替换法呢？看下图就能明白了：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231109234625186.png" alt="image-20231109234625186"></p>
<ul>
<li>这个<strong>高亮字段</strong>是我们直接获取的，<strong>官方文档</strong>中也直接给了 demo 示例代码：</li>
</ul>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs routeros">hit.getHighlightFields().<span class="hljs-built_in">get</span>(<span class="hljs-string">&quot;title&quot;</span>)<br></code></pre></td></tr></table></figure>

<ul>
<li><strong>官方文档</strong>：[Highlighting | Elasticsearch Guide <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/highlighting.html">7.17] | Elastic</a></li>
<li>总算完整地过了一遍<strong>关键词高亮显示流程</strong>了 <strong>（2023/11/09晚）</strong></li>
</ul>
<h2 id="博文阅读页面开发"><a href="#博文阅读页面开发" class="headerlink" title="博文阅读页面开发"></a>博文阅读页面开发</h2><ul>
<li><a target="_blank" rel="noopener" href="https://so.gushiwen.cn/mingjus/default.aspx?cstr=%E4%BA%94%E4%BB%A3">五代名句_古诗文网 (gushiwen.cn)</a></li>
<li>现在有诗词和博文两个聚合搜素，关于这两类数据的数据来源，想法是这样的：<ul>
<li>博文就直接存储在本地数据库，因为在其他网站（掘金 / CSDN）同步到的博文信息有所欠缺（文章创作时间等等）</li>
<li>因为我想借这个机会，不仅将博文搜索接入聚合搜索中，还想实现基本的博文阅读功能</li>
<li>诗词搜索就直接调用外部接口，再同步到本地数据库，古诗词网就很不错</li>
</ul>
</li>
</ul>
<h3 id="博文阅读页跳转"><a href="#博文阅读页跳转" class="headerlink" title="博文阅读页跳转"></a>博文阅读页跳转</h3><ul>
<li><p>一切都很顺利，直接粘贴 <strong>Memory 缘忆交友社区</strong>的相关业务逻辑代码即可</p>
</li>
<li><p>奶奶的，全局响应拦截器害了我：</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 添加响应拦截器</span><br>instance.interceptors.response.use(<br>    function (response) &#123;<br>        <span class="hljs-comment">// 2xx 范围内的状态码都会触发该函数。</span><br>        <span class="hljs-comment">// 对响应数据做点什么</span><br>        <span class="hljs-type">const</span> <span class="hljs-variable">data</span> <span class="hljs-operator">=</span> response.data;<br>        <span class="hljs-keyword">if</span> (data.code === <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-keyword">return</span> data.data;<br>        &#125;<br>        console.error(<span class="hljs-string">&quot;request error&quot;</span>, data);<br>        <span class="hljs-keyword">return</span> response.data;<br>    &#125;,<br>    function (error) &#123;<br>        <span class="hljs-comment">// 超出 2xx 范围的状态码都会触发该函数。</span><br>        <span class="hljs-comment">// 对响应错误做点什么</span><br>        <span class="hljs-keyword">return</span> Promise.reject(error);<br>    &#125;<br>);<br></code></pre></td></tr></table></figure>

<ul>
<li>只好是这样接收文章了</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">const</span> <span class="hljs-title function_">getArticle</span> = (<span class="hljs-params"></span>) =&gt; &#123;<br>  myAxios.<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;/article/get/VO&quot;</span>, &#123;<br>    <span class="hljs-attr">params</span>: &#123;<br>      <span class="hljs-attr">id</span>: articleId.<span class="hljs-property">value</span><br>    &#125;<br>  &#125;).<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> &#123;<br>    articleInfo.<span class="hljs-property">value</span> = res;<br><br>    <span class="hljs-keyword">const</span> md = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MarkdownIt</span>();<br>    parsedContent.<span class="hljs-property">value</span> = md.<span class="hljs-title function_">render</span>(articleInfo.<span class="hljs-property">value</span>.<span class="hljs-property">content</span>);<br>  &#125;).<span class="hljs-title function_">catch</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;获取文章信息失败&quot;</span>)<br>  &#125;);<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231111173705877.png" alt="image-20231111173705877"></p>
<ul>
<li>效果还可以，不过我设想的是这个搜索平台不需要登陆，用户就能享受所有服务</li>
<li>之后会<strong>把用户相关业务全部优化掉</strong> <strong>（2023/11/11晚）</strong></li>
</ul>
<h2 id="图片聚合搜素"><a href="#图片聚合搜素" class="headerlink" title="图片聚合搜素"></a>图片聚合搜素</h2><ul>
<li>了解下 jsoup 工具如何实现<strong>网页抓取</strong>，并使用 HttpClient 实现<strong>解析网页内容</strong></li>
<li>开发全新的聚合搜索：视频聚合搜索</li>
</ul>
<h2 id="诗词聚合搜索"><a href="#诗词聚合搜索" class="headerlink" title="诗词聚合搜索"></a>诗词聚合搜索</h2><ul>
<li>请求古诗词网，获取诗词（如下图 👇）<strong>（2023/11/12晚）</strong></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231112185151909.png" alt="image-20231112185151909"></p>
<ul>
<li><strong>示例代码</strong>如下：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br>   <span class="hljs-keyword">void</span> <span class="hljs-title function_">testFetchPoem</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>       <span class="hljs-comment">// 1. 获取数据</span><br>       <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;https://so.gushiwen.cn/shiwens/default.aspx?page=6&amp;astr=%E6%9D%9C%E7%94%AB&quot;</span>;<br>       <span class="hljs-type">Document</span> <span class="hljs-variable">doc</span> <span class="hljs-operator">=</span> Jsoup.connect(url)<br>               .userAgent(<span class="hljs-string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.62&quot;</span>)<br>               .get();<br>       <span class="hljs-comment">// 输出整个 HTML 文档</span><br>       System.out.println(doc);<br>       <br>       <span class="hljs-comment">// 捕获 class = &quot;titletype&quot;</span><br>       <span class="hljs-type">Elements</span> <span class="hljs-variable">elements</span> <span class="hljs-operator">=</span> doc.select(<span class="hljs-string">&quot;.titletype&quot;</span>);<br>       <span class="hljs-keyword">for</span> (Element element : elements) &#123;<br>           System.out.println(element);<br>           System.out.println(element.text());<br><br>       &#125;<br><br>       <span class="hljs-comment">// 捕获 id = &quot;leftZhankai&quot;</span><br>       <span class="hljs-type">Element</span> <span class="hljs-variable">leftZhankai</span> <span class="hljs-operator">=</span> doc.getElementById(<span class="hljs-string">&quot;leftZhankai&quot;</span>);<br>       System.out.println(leftZhankai);<br>       System.out.println(leftZhankai.text());<br>   &#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231112185139366.png" alt="image-20231112185139366"></p>
<h3 id="CSS-选择器巩固"><a href="#CSS-选择器巩固" class="headerlink" title="CSS 选择器巩固"></a>CSS 选择器巩固</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 捕获 id = &quot;leftZhankai&quot;</span><br>       <span class="hljs-type">Element</span> <span class="hljs-variable">leftZhankai</span> <span class="hljs-operator">=</span> doc.getElementById(<span class="hljs-string">&quot;leftZhankai&quot;</span>);<br>       <span class="hljs-type">Elements</span> <span class="hljs-variable">heads</span> <span class="hljs-operator">=</span> leftZhankai.select(<span class="hljs-string">&quot;.sons .cont div:nth-of-type(2)&quot;</span>);<br>       <span class="hljs-keyword">for</span> (Element head : heads) &#123;<br>           <span class="hljs-type">Elements</span> <span class="hljs-variable">title</span> <span class="hljs-operator">=</span> head.select(<span class="hljs-string">&quot;&gt;p:nth-of-type(2)&quot;</span>);<br>           <span class="hljs-comment">// Elements author = head.select(&quot;p:nth-of-type(2)&quot;);</span><br>           <span class="hljs-comment">// System.out.println(title.text() + &quot; &quot; + author.text());</span><br>           System.out.println(<span class="hljs-string">&quot;hhh&quot;</span> + title.text());<br>       &#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231112200526767.png" alt="image-20231112200526767"></p>
<ul>
<li>熟悉 <strong>CSS 选择器</strong>之后，<strong>解析 HTML 文档</strong>获取<strong>标题、诗人和内容</strong>就很轻松了：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231112202249521.png" alt="image-20231112202249521"></p>
<h3 id="问题总结"><a href="#问题总结" class="headerlink" title="问题总结"></a>问题总结</h3><ul>
<li>基本实现了诗词聚合搜索，但有两个问题待解决：<ul>
<li>logstash 数据数据同步，output 块和 input 块没有一一对应</li>
<li>可能由于分词机制，我搜索“杜甫”关键词，为什么只匹配一条数据呢</li>
<li>挺奇怪的，不过今天就到这里了<strong>（2023/11/12晚）</strong></li>
</ul>
</li>
</ul>
<h2 id="阶段性问题解决"><a href="#阶段性问题解决" class="headerlink" title="阶段性问题解决"></a>阶段性问题解决</h2><h3 id="搜索“杜甫”未匹配文档的问题"><a href="#搜索“杜甫”未匹配文档的问题" class="headerlink" title="搜索“杜甫”未匹配文档的问题"></a>搜索“杜甫”未匹配文档的问题</h3><ul>
<li>找到了，<strong>使用搜索框输入关键词</strong>执行搜索时，没有匹配 <code>author</code> 字段，补充这段代码即可：<strong>（2023/11/13晚）</strong></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 按关键词检索 满足其一√</span><br>     <span class="hljs-keyword">if</span> (StringUtils.isNotBlank(searchText)) &#123;<br>         boolQueryBuilder.should(QueryBuilders.matchQuery(<span class="hljs-string">&quot;title&quot;</span>, searchText));<br>         boolQueryBuilder.should(QueryBuilders.matchQuery(<span class="hljs-string">&quot;content&quot;</span>, searchText));<br>         boolQueryBuilder.should(QueryBuilders.matchQuery(<span class="hljs-string">&quot;author&quot;</span>, searchText));<br>         boolQueryBuilder.minimumShouldMatch(<span class="hljs-number">1</span>);<br>     &#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231113213844236.png" alt="image-20231113213844236"></p>
<h3 id="无搜索关键词匹配文档过少"><a href="#无搜索关键词匹配文档过少" class="headerlink" title="无搜索关键词匹配文档过少"></a>无搜索关键词匹配文档过少</h3><ul>
<li>在 kibana 面板中，执行这样的 DSL 查询语句：<strong>（2023/11/13晚）</strong></li>
</ul>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs routeros"><span class="hljs-built_in">GET</span> post_v1/_search<br>&#123;<br> <span class="hljs-string">&quot;query&quot;</span> : &#123;<br>    <span class="hljs-string">&quot;match_all&quot;</span>: &#123;&#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>查询结果如下，显示 post_v1 索引下一共有 64 条文档：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231113214120819.png" alt="image-20231113214120819"></p>
<ul>
<li>也可以执行以下语句确认文档条数：</li>
</ul>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs routeros"><span class="hljs-built_in">GET</span> post_v1/_count<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231113214149713.png" alt="image-20231113214149713"></p>
<ul>
<li><p>而在前端面板执行默认空关键词查询时，仅显示 15 条数据，这不是我们想看到的</p>
</li>
<li><p>妈的，查到原因了，这里搞了个<strong>分页查询</strong>：</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 分页</span><br>       <span class="hljs-type">PageRequest</span> <span class="hljs-variable">pageRequest</span> <span class="hljs-operator">=</span> PageRequest.of((<span class="hljs-type">int</span>) current, (<span class="hljs-type">int</span>) pageSize);<br>       <span class="hljs-comment">// 构造查询</span><br>       <span class="hljs-type">NativeSearchQuery</span> <span class="hljs-variable">searchQuery</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NativeSearchQueryBuilder</span>()<br>               .withQuery(boolQueryBuilder)<br>               .withHighlightBuilder(highlightBuilder)<br>               .withPageable(pageRequest)<br>               .withSorts(sortBuilder).build();<br>       SearchHits&lt;PostEsDTO&gt; searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class);<br></code></pre></td></tr></table></figure>

<ul>
<li>查询这个 pageSize，发现来源于前端的传参：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">const</span> initSearchParams = &#123;<br>  <span class="hljs-attr">type</span>: activeKey,<br>  <span class="hljs-attr">text</span>: <span class="hljs-string">&quot;&quot;</span>,<br>  <span class="hljs-attr">pageSize</span>: <span class="hljs-number">15</span>,<br>  <span class="hljs-attr">pageNum</span>: <span class="hljs-number">1</span>,<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>这个就应该保留，而前端应该加个分页插件</li>
<li>奶奶的，分页插件不好做，换页后，要重新在 post 页面重新执行聚合查询，还得改动不少代码</li>
<li>算了，不是什么核心功能，改天再完善</li>
</ul>
<h3 id="博文-ES-搜索，查询结果为空"><a href="#博文-ES-搜索，查询结果为空" class="headerlink" title="博文 ES 搜索，查询结果为空"></a>博文 ES 搜索，查询结果为空</h3><ul>
<li>奇了怪了，Kibana 面板执行查询，显示有82条数据的：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231115224200545.png" alt="image-20231115224200545"></p>
<ul>
<li>啊？他爷爷的，隔了一晚上，这数据就变了是吧：</li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20231116152040439.png" alt="image-20231116152040439"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231116152213010.png" alt="image-20231116152213010"></p>
<ul>
<li>这就很奇怪了，可能是我昨晚没有开启 logstash 吧，但这个配置还没做好，目前正在研究如何使<strong>多组 input / output 块一一对应</strong></li>
<li><a target="_blank" rel="noopener" href="https://www.python100.com/html/75492.html">logstash启动命令_笔记大全_设计学院 (python100.com)</a></li>
</ul>
<h3 id="Logstash-配置多个输入输出源Ⅱ"><a href="#Logstash-配置多个输入输出源Ⅱ" class="headerlink" title="Logstash 配置多个输入输出源Ⅱ"></a>Logstash 配置多个输入输出源Ⅱ</h3><ul>
<li><p>如何配置多个 input 块和 output 块？很简单的问题，多写几个配置文件就可以了：</p>
</li>
<li><h5 id="myTask-conf："><a href="#myTask-conf：" class="headerlink" title="myTask.conf："></a><code>myTask.conf</code>：</h5></li>
</ul>
<figure class="highlight puppet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs puppet"><span class="hljs-comment"># Sample Logstash configuration for receiving</span><br><span class="hljs-comment"># UDP syslog messages over port 514</span><br><br><span class="hljs-keyword">input</span> &#123;<br>  jdbc &#123;<br>    <span class="hljs-attr">jdbc_driver_library</span> =&gt; <span class="hljs-string">&quot;D:\softWare\logstash\logstash-7.17.9\config\mysql-connector-java-8.0.29.jar&quot;</span><br>    <span class="hljs-attr">jdbc_driver_class</span> =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    <span class="hljs-attr">jdbc_connection_string</span> =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/memory_search&quot;</span><br>    <span class="hljs-attr">jdbc_user</span> =&gt; <span class="hljs-string">&quot;root&quot;</span><br>    <span class="hljs-attr">jdbc_password</span> =&gt; <span class="hljs-string">&quot;Dw990831&quot;</span><br>    <span class="hljs-attr">statement</span> =&gt; <span class="hljs-string">&quot;SELECT * from post where updateTime &gt; :sql_last_value and updateTime &lt; now() order by updateTime desc&quot;</span><br>    <span class="hljs-attr">use_column_value</span> =&gt; <span class="hljs-keyword">true</span><br>    <span class="hljs-attr">tracking_column_type</span> =&gt; <span class="hljs-string">&quot;timestamp&quot;</span><br>    <span class="hljs-attr">tracking_column</span> =&gt; <span class="hljs-string">&quot;updatetime&quot;</span><br>    <span class="hljs-attr">schedule</span> =&gt; <span class="hljs-string">&quot;*/5 * * * * *&quot;</span><br>    <span class="hljs-attr">jdbc_default_timezone</span> =&gt; <span class="hljs-string">&quot;Asia/Shanghai&quot;</span><br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">filter</span> &#123;<br>  mutate &#123;<br>    <span class="hljs-attr">rename</span> =&gt; &#123;<br>      <span class="hljs-string">&quot;updatetime&quot;</span> =&gt; <span class="hljs-string">&quot;updateTime&quot;</span><br>      <span class="hljs-string">&quot;userid&quot;</span> =&gt; <span class="hljs-string">&quot;userId&quot;</span><br>      <span class="hljs-string">&quot;createtime&quot;</span> =&gt; <span class="hljs-string">&quot;createTime&quot;</span><br>      <span class="hljs-string">&quot;isdelete&quot;</span> =&gt; <span class="hljs-string">&quot;isDelete&quot;</span><br>    &#125;<br>    <span class="hljs-keyword">remove_field</span> =&gt; [<span class="hljs-string">&quot;thumbnum&quot;</span>, <span class="hljs-string">&quot;favournum&quot;</span>]<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">output</span> &#123;<br>  stdout &#123; <span class="hljs-attr">codec</span> =&gt; rubydebug &#125;<br>    <span class="hljs-keyword">elasticsearch</span> &#123;<br>    <span class="hljs-attr">hosts</span> =&gt; <span class="hljs-string">&quot;127.0.0.1:9200&quot;</span><br>    <span class="hljs-attr">index</span> =&gt; <span class="hljs-string">&quot;post_v1&quot;</span><br>    <span class="hljs-attr">document_id</span> =&gt; <span class="hljs-string">&quot;%&#123;id&#125;&quot;</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><code>myTask2.conf</code>：</li>
</ul>
<figure class="highlight puppet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs puppet"><span class="hljs-keyword">input</span> &#123;<br>  jdbc &#123;<br>    <span class="hljs-attr">jdbc_driver_library</span> =&gt; <span class="hljs-string">&quot;D:\softWare\logstash\logstash-7.17.9\config\mysql-connector-java-8.0.29.jar&quot;</span><br>    <span class="hljs-attr">jdbc_driver_class</span> =&gt; <span class="hljs-string">&quot;com.mysql.jdbc.Driver&quot;</span><br>    <span class="hljs-attr">jdbc_connection_string</span> =&gt; <span class="hljs-string">&quot;jdbc:mysql://localhost:3306/memory_search&quot;</span><br>    <span class="hljs-attr">jdbc_user</span> =&gt; <span class="hljs-string">&quot;root&quot;</span><br>    <span class="hljs-attr">jdbc_password</span> =&gt; <span class="hljs-string">&quot;Dw990831&quot;</span><br>    <span class="hljs-attr">statement</span> =&gt; <span class="hljs-string">&quot;SELECT * from article where updateTime &gt; :sql_last_value and updateTime &lt; now() order by updateTime desc&quot;</span><br>    <span class="hljs-attr">use_column_value</span> =&gt; <span class="hljs-keyword">true</span><br>    <span class="hljs-attr">tracking_column_type</span> =&gt; <span class="hljs-string">&quot;timestamp&quot;</span><br>    <span class="hljs-attr">tracking_column</span> =&gt; <span class="hljs-string">&quot;updatetime&quot;</span><br>    <span class="hljs-attr">schedule</span> =&gt; <span class="hljs-string">&quot;*/5 * * * * *&quot;</span><br>    <span class="hljs-attr">jdbc_default_timezone</span> =&gt; <span class="hljs-string">&quot;Asia/Shanghai&quot;</span><br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">filter</span> &#123;<br>  mutate &#123;<br>    <span class="hljs-attr">rename</span> =&gt; &#123;<br>      <span class="hljs-string">&quot;updatetime&quot;</span> =&gt; <span class="hljs-string">&quot;updateTime&quot;</span><br>      <span class="hljs-string">&quot;userid&quot;</span> =&gt; <span class="hljs-string">&quot;userId&quot;</span><br>      <span class="hljs-string">&quot;createtime&quot;</span> =&gt; <span class="hljs-string">&quot;createTime&quot;</span><br>      <span class="hljs-string">&quot;isdelete&quot;</span> =&gt; <span class="hljs-string">&quot;isDelete&quot;</span><br>    &#125;<br>    <span class="hljs-keyword">remove_field</span> =&gt; [<span class="hljs-string">&quot;thumbnum&quot;</span>, <span class="hljs-string">&quot;favournum&quot;</span>]<br>  &#125;<br>&#125;<br><br><span class="hljs-keyword">output</span> &#123;<br>  stdout &#123; <span class="hljs-attr">codec</span> =&gt; rubydebug &#125;<br>    <span class="hljs-keyword">elasticsearch</span> &#123;  <br>    <span class="hljs-attr">hosts</span> =&gt; [<span class="hljs-string">&quot;127.0.0.1:9200&quot;</span>]  <br>    <span class="hljs-attr">index</span> =&gt; <span class="hljs-string">&quot;article_v1&quot;</span>  <br>    <span class="hljs-attr">document_id</span> =&gt; <span class="hljs-string">&quot;%&#123;id&#125;&quot;</span>  <br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>执行这条命令即可：</li>
</ul>
<figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs livescript">.<span class="hljs-string">\bin\logstash.bat</span> -f .<span class="hljs-string">\config\myTask.conf</span> -f .<span class="hljs-string">\config\myTask2.conf</span><br></code></pre></td></tr></table></figure>

<ul>
<li>有关 Logstash 的配置，还需要更多了解，目前知识掌握了 MSQL 向 ES 的映射配置<strong>（2023/11/16晚）</strong></li>
</ul>
<h3 id="Logstash-配置多个输入输出源Ⅲ"><a href="#Logstash-配置多个输入输出源Ⅲ" class="headerlink" title="Logstash 配置多个输入输出源Ⅲ"></a>Logstash 配置多个输入输出源Ⅲ</h3><blockquote>
<p>🍖 推荐阅读：<a target="_blank" rel="noopener" href="http://www.51niux.com/?id=205">柴少的官方网站-Logstash多配置文件启动（五） (51niux.com)</a></p>
</blockquote>
<ul>
<li>今天上午，发现之前的命令并不能够同时加载多个配置文件，经过两个小时的努力，终于找到了解决之道：</li>
<li>我把本地的两个配置文件 <code>myTask.conf</code> 和 <code>myTask.conf2</code> 放在同一个文件夹下 <code>conf</code>：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231226111957531.png" alt="image-20231226111957531"></p>
<ul>
<li>执行以下命令：</li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">.\bin\logstash.bat -f .\config\conf<br></code></pre></td></tr></table></figure>

<ul>
<li>很显然，两个配置文件都被成功加载到了：<strong>（2023/12/26早）</strong></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231226112125543.png" alt="image-20231226112125543"></p>
<h2 id="搜索建议"><a href="#搜索建议" class="headerlink" title="搜索建议"></a>搜索建议</h2><ul>
<li>还需要<strong>安装 suggestion 插件</strong>，这里给出 demo 示例代码：</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs sql">POST post_v1<span class="hljs-operator">/</span>_search<br>&#123;<br>  &quot;query&quot; : &#123;<br>     &quot;match&quot;: &#123; &quot;content&quot;: &quot;公孙&quot; &#125;<br>  &#125;,<br>  &quot;suggest&quot; : &#123;<br>    &quot;my-suggestion&quot; : &#123;<br>      &quot;text&quot; : &quot;公孙&quot;,<br>      &quot;term&quot; : &#123;<br>        &quot;field&quot; : &quot;content&quot;<br>      &#125;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<h2 id="优化图片搜索体验"><a href="#优化图片搜索体验" class="headerlink" title="优化图片搜索体验"></a>优化图片搜索体验</h2><ul>
<li>首先优化执行关键词检索后的反馈：<strong>（2023/11/16晚）</strong></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;post&quot;</span>) &#123;<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功检索出您感兴趣的诗词~&quot;</span>)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;user&quot;</span>) &#123;<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功检索出您感兴趣的用户~&quot;</span>)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;picture&quot;</span>) &#123;<br>    pictureList.<span class="hljs-property">value</span> = [];<br>    <span class="hljs-title function_">getImages</span>()<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;article&quot;</span>) &#123;<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功检索出您感兴趣的博文~&quot;</span>)<br>  &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>提供检索图片的下载地址，用户<strong>可直接复制该地址</strong>：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231116220007316.png" alt="image-20231116220007316"></p>
<ul>
<li>又是经典的点击按钮后，<strong>仅展示所属弹窗</strong>问题（<strong>绑定唯一值</strong>，这里是 id）：</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;!--下载--&gt;<br>          &lt;div&gt;<br>            &lt;a-button @click=&quot;showModal(item.title)&quot; type=&quot;primary&quot; shape=&quot;round&quot;&gt;<br>              &lt;template #icon&gt;<br>                &lt;DownloadOutlined/&gt;<br>                Download<br>              &lt;/template&gt;<br>            &lt;/a-button&gt;<br>            &lt;a-modal v-model:visible=&quot;visible[item.title]&quot; title=&quot;Basic Modal&quot; @ok=&quot;handleOk(item.title)&quot;&gt;<br>              &lt;p&gt;&#123;&#123; item.url &#125;&#125;&lt;/p&gt;<br>            &lt;/a-modal&gt;<br>          &lt;/div&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs ts"><span class="hljs-comment">// 点击弹窗</span><br><span class="hljs-keyword">const</span> visible = <span class="hljs-title function_">ref</span>(&#123;&#125;)<br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">showModal</span> = (<span class="hljs-params">id: <span class="hljs-built_in">any</span></span>) =&gt; &#123;<br>  visible.<span class="hljs-property">value</span>[id] = <span class="hljs-literal">true</span>;<br>&#125;;<br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">handleOk</span> = (<span class="hljs-params">id: <span class="hljs-built_in">any</span></span>) =&gt; &#123;<br>  visible.<span class="hljs-property">value</span>[id] = <span class="hljs-literal">true</span>;<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>将来引入第三方库，实现<strong>一键复制图片地址功能</strong> <strong>（2023/11/16晚）</strong></li>
</ul>
<h2 id="代码优化，删除冗余代码"><a href="#代码优化，删除冗余代码" class="headerlink" title="代码优化，删除冗余代码"></a>代码优化，删除冗余代码</h2><ul>
<li>干掉所有 User 相关代码</li>
<li>可以了，代码算是删干净了</li>
<li>后续几周内会逐步增加视频聚合搜索功能，并逐步上线该项目</li>
<li>那么接下来，就是优化 Memory API 接口开放平台了<strong>（2023/11/18午）</strong></li>
</ul>
<h2 id="Kibana-监控面板"><a href="#Kibana-监控面板" class="headerlink" title="Kibana 监控面板"></a>Kibana 监控面板</h2><ul>
<li><p>其实没什么好讲的，不过还是稍微体验了一下：</p>
</li>
<li><p>简单地记录一下吧：</p>
</li>
<li><h5 id="找到监控看板："><a href="#找到监控看板：" class="headerlink" title="找到监控看板："></a>找到监控看板：</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203121821257.png" alt="image-20231203121821257"></p>
<ul>
<li><h5 id="创建可视化看板"><a href="#创建可视化看板" class="headerlink" title="创建可视化看板"></a>创建可视化看板</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203122616094.png" alt="image-20231203122616094"></p>
<ul>
<li><h5 id="进入看板管理页面"><a href="#进入看板管理页面" class="headerlink" title="进入看板管理页面"></a>进入看板管理页面</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203122622671.png" alt="image-20231203122622671"></p>
<ul>
<li><h5 id="查看已创建的看板列表，创建新的看板"><a href="#查看已创建的看板列表，创建新的看板" class="headerlink" title="查看已创建的看板列表，创建新的看板"></a>查看已创建的看板列表，创建新的看板</h5></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203122626820.png" alt="image-20231203122626820"></p>
<ul>
<li><h5 id="创建新的看板"><a href="#创建新的看板" class="headerlink" title="创建新的看板"></a>创建新的看板</h5></li>
</ul>
<blockquote>
<p>如下图所示：</p>
<p>看板的命名很有意思，<strong>看板的命名必须要匹配到已经创建的索引名</strong>，还不能重复，也就是说：</p>
<p><strong>每个索引只可以创建一个看板</strong>，至少我目前的看法是这样的<strong>（2023/12/03午）</strong></p>
</blockquote>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203122632293.png" alt="image-20231203122632293"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203122639112.png" alt="image-20231203122639112"></p>
<ul>
<li>创建好新的看板之后，就可以再次进入 <code>DashBorad</code> 界面了，我们创建的可视化看板可以投入使用了</li>
<li>这里<strong>简单地介绍下各个板块的作用</strong>吧，其他没有什么好讲的，有时间玩玩就可以</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203123414249.png" alt="image-20231203123414249"></p>
<ul>
<li>使用 <code>Kibana</code> 可视化监控看板的教程到这里就结束了<strong>（2023/12/03午）</strong></li>
</ul>
<h2 id="博文搜索优化"><a href="#博文搜索优化" class="headerlink" title="博文搜索优化"></a>博文搜索优化</h2><ul>
<li>今天晚上，总算抽出时间，着手优化一下博文搜索这一大板块了：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231220234611498.png" alt="image-20231220234611498"></p>
<ul>
<li>页面优化完成：（2023/12/21晚）</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231222000308888.png" alt="image-20231222000308888"></p>
<h3 id="Vue-实现外部引入页面组件"><a href="#Vue-实现外部引入页面组件" class="headerlink" title="Vue 实现外部引入页面组件"></a>Vue 实现外部引入页面组件</h3><ul>
<li>外部页面组件 <code>ArticleList</code> 携带参数，参数名为 <code>compreList</code>，参数值为 <code>articleList</code></li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-tabs v-model:activeKey=&quot;activeKey&quot; tab-position=&quot;left&quot;&gt;<br>        &lt;a-tab-pane key=&quot;1&quot; tab=&quot;综合&quot;&gt;<br>          &lt;CompreArticleList :compre-list=&quot;articleList&quot;/&gt;<br>        &lt;/a-tab-pane&gt;<br>        &lt;a-tab-pane key=&quot;2&quot; tab=&quot;后端&quot;&gt;<br>        ............................<br> &lt;/a-tabs&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li>编写内部组件 <code>CompreArticleList</code>，接收参数 <code>compreList</code>，使用 <code>a-list</code>组件展示</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs vue"> &lt;a-list<br>          class=&quot;demo-loadmore-list&quot;<br>          item-layout=&quot;horizontal&quot;<br>          :data-source=&quot;props.compreList&quot;<br>          style=&quot;width: 400%&quot;<br>      &gt;<br>  ................................<br>&lt;/a-list&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li>指明接收参数名</li>
</ul>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs tsx">&lt;script setup lang=<span class="hljs-string">&quot;ts&quot;</span>&gt;<br><span class="hljs-keyword">import</span> &#123;defineProps, withDefaults&#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;vue/dist/vue&quot;</span>;<br><br>..........................................<br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">Props</span> &#123;<br>  <span class="hljs-attr">compreList</span>: <span class="hljs-built_in">any</span>[];<br>&#125;<br><br><span class="hljs-keyword">const</span> props = <span class="hljs-title function_">withDefaults</span>(defineProps&lt;<span class="hljs-title class_">Props</span>&gt;(), &#123;<br>  <span class="hljs-attr">compreList</span>: <span class="hljs-function">() =&gt;</span> [],<br>&#125;);<br><br>&lt;/script&gt;<br></code></pre></td></tr></table></figure>

<h2 id="爬取掘金热榜文章"><a href="#爬取掘金热榜文章" class="headerlink" title="爬取掘金热榜文章"></a>爬取掘金热榜文章</h2><ul>
<li>基本完成文章爬取测试</li>
</ul>
<h3 id="分析掘金热榜博文"><a href="#分析掘金热榜博文" class="headerlink" title="分析掘金热榜博文"></a>分析掘金热榜博文</h3><p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231222125049450.png" alt="image-20231222125049450"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231222125059824.png" alt="image-20231222125059824"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231222125104035.png" alt="image-20231222125104035"></p>
<h3 id="编写爬虫"><a href="#编写爬虫" class="headerlink" title="编写爬虫"></a>编写爬虫</h3><ul>
<li>使用 Hutool 工具库，实现 I/O 爬虫，获取掘金热榜中的所有文章 id：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">getArticles</span><span class="hljs-params">()</span> &#123;<br>	<span class="hljs-comment">// 携带参数，发起请求，获得 json 数据</span><br>    <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;https://api.juejin.cn/content_api/v1/content/article_rank?category_id=6809637769959178254&amp;type=hot&amp;aid=2608&amp;uuid=7202969973525005828&amp;spider=0&quot;</span>;<br>    <span class="hljs-type">HttpRequest</span> <span class="hljs-variable">request</span> <span class="hljs-operator">=</span> HttpRequest.get(url);<br>    <span class="hljs-type">HttpResponse</span> <span class="hljs-variable">response</span> <span class="hljs-operator">=</span> request.execute();<br>    <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> response.body();<br>    System.out.println(json);<br><br>    System.out.println(<span class="hljs-string">&quot;------------------------&quot;</span>);<br>		<br> <span class="hljs-comment">// 使用 Jackson 的 objectMapper，解析 json 数据</span><br>    <span class="hljs-type">ObjectMapper</span> <span class="hljs-variable">objectMapper</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ObjectMapper</span>();<br>    <span class="hljs-type">JsonNode</span> <span class="hljs-variable">rootNode</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>    <span class="hljs-keyword">try</span> &#123;<br>        rootNode = objectMapper.readTree(json);<br>    &#125; <span class="hljs-keyword">catch</span> (JsonProcessingException e) &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>    &#125;<br><br>    <span class="hljs-type">JsonNode</span> <span class="hljs-variable">dataNode</span> <span class="hljs-operator">=</span> rootNode.get(<span class="hljs-string">&quot;data&quot;</span>);<br>    <span class="hljs-comment">// 拿取所有文章 id</span><br>    <span class="hljs-keyword">for</span> (JsonNode jsonNode : dataNode) &#123;<br>        <span class="hljs-type">JsonNode</span> <span class="hljs-variable">contentNode</span> <span class="hljs-operator">=</span> jsonNode.get(<span class="hljs-string">&quot;content&quot;</span>);<br>        <span class="hljs-type">String</span> <span class="hljs-variable">contentId</span> <span class="hljs-operator">=</span> contentNode.get(<span class="hljs-string">&quot;content_id&quot;</span>).asText();<br>        System.out.println(<span class="hljs-string">&quot;content_id: &quot;</span> + contentId);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>拿到掘金文章 id 后，使用 <code>jsoup</code> 库发起请求，获取 HTML 文档，并解析获得文章数据：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">getContents</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-comment">// 1. 获取数据, 获取 HTML 文档</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;https://juejin.cn/post/7312376672665075722&quot;</span>;<br>        <span class="hljs-type">Document</span> <span class="hljs-variable">doc</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            doc = Jsoup.connect(url)<br>                    .userAgent(<span class="hljs-string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.81&quot;</span>)<br>                    .get();<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>        &#125;<br><br>        <span class="hljs-comment">// 输出整个 HTML 文档</span><br>        <span class="hljs-comment">// System.out.println(doc);</span><br><br>	    <span class="hljs-comment">// CSS 选择器获取数据</span><br>        <span class="hljs-type">Elements</span> <span class="hljs-variable">title</span> <span class="hljs-operator">=</span> doc.select(<span class="hljs-string">&quot;title&quot;</span>);<br>        System.out.println(<span class="hljs-string">&quot;根据标签: &quot;</span> + title);<br><br>        <span class="hljs-type">Elements</span> <span class="hljs-variable">select</span> <span class="hljs-operator">=</span> doc.select(<span class="hljs-string">&quot;.article-area .article-title&quot;</span>);<br>        System.out.println(<span class="hljs-string">&quot;根据 class: &quot;</span> + select);<br><br>        System.out.println(<span class="hljs-string">&quot;----------------------------------&quot;</span>);<br>        <span class="hljs-type">Element</span> <span class="hljs-variable">content</span> <span class="hljs-operator">=</span> doc.getElementById(<span class="hljs-string">&quot;article-root&quot;</span>);<br>        System.out.println(content);<br>    &#125;<br></code></pre></td></tr></table></figure>

<h3 id="爬取结果"><a href="#爬取结果" class="headerlink" title="爬取结果"></a>爬取结果</h3><p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231222000216336.png" alt="image-20231222000216336"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231222000231956.png" alt="image-20231222000231956"></p>
<ul>
<li>记录一下旧的数据：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231222215255379.png" alt="image-20231222215255379"></p>
<h3 id="优化思路"><a href="#优化思路" class="headerlink" title="优化思路"></a>优化思路</h3><ul>
<li>博文来源：爬取掘金文章榜<strong>（2023/12/20晚）</strong></li>
<li>目前看来每个文章类别下有 20 篇文章，那就创建一张表吧<ul>
<li>按照类别，可以将博文简单分类</li>
<li>聚合搜索时，额外添加一个条件：根据文章类别查询到对应的博文，这就需要把对博文的全局搜索推迟到 <code>ArticleList</code> 实现了</li>
<li>数据同步是这个项目的一大亮点，使用 <code>Logstash</code> 进行数据同步在本地测试已经很成熟了，可以考虑采用 <code>canal</code> 方法实现</li>
<li>搜索建议和关键词高亮，这也该项目的一大亮点，如果能成功完成就好了</li>
<li>限流，四种限流算法，就这么个小项目，虽然远不至于用到限流，但是学习限流还是很有必要的</li>
</ul>
</li>
<li>写一个定时爬虫，定时爬取并更新博文数据</li>
<li>开发者文档：Memory-Tools 开发者文档，这个文档的成功开发部署，使得这个工作变得稍微简单了一些</li>
</ul>
<h2 id="存储数据库编码错误"><a href="#存储数据库编码错误" class="headerlink" title="存储数据库编码错误"></a>存储数据库编码错误</h2><h3 id="问题引出"><a href="#问题引出" class="headerlink" title="问题引出"></a>问题引出</h3><ul>
<li>保存的内容存在图标的时候：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231223221304769.png" alt="image-20231223221304769"></p>
<ul>
<li>会直接报错：</li>
</ul>
<figure class="highlight clean"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs clean">org.springframework.jdbc.UncategorizedSQLException: <br>### Error updating database.  Cause: java.sql.SQLException: Incorrect string value: <span class="hljs-string">&#x27;\xF0\x9F\x8D\x84 \xE5...&#x27;</span> for column <span class="hljs-string">&#x27;content&#x27;</span> at row <span class="hljs-number">1</span><br>### The error may exist <span class="hljs-keyword">in</span> com/memory/search/mapper/ArticleMapper.java (best guess)<br>### The error may involve com.memory.search.mapper.ArticleMapper.insert-Inline<br></code></pre></td></tr></table></figure>

<p>这是因为保存到数据库中的内容中，包含了不能正确解码的内容， 这就是<strong>直接保存</strong>，详细情况如下：</p>
<h4 id="直接保存"><a href="#直接保存" class="headerlink" title="直接保存"></a>直接保存</h4><p>数据库中，文章内容 content 字段属性为 varchar，用来保存字符串：<strong>（2023/12/24早）</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 文章内容</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> String content;<br></code></pre></td></tr></table></figure>

<p>爬取到文章内容，直接保存文章内容：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs java"> <span class="hljs-comment">// 1. 获取数据</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;https://juejin.cn/post/7313418992310976549&quot;</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-type">Document</span> <span class="hljs-variable">doc</span> <span class="hljs-operator">=</span> Jsoup.connect(url)<br>                    .userAgent(<span class="hljs-string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.81&quot;</span>)<br>                    .get();<br><br><br>            <span class="hljs-type">Elements</span> <span class="hljs-variable">title</span> <span class="hljs-operator">=</span> doc.select(<span class="hljs-string">&quot;.article-area .article-title&quot;</span>);<br>            System.out.println(<span class="hljs-string">&quot;----------------博文标题----------------&quot;</span>);<br>            System.out.println(title.text());<br><br>            <span class="hljs-type">Elements</span> <span class="hljs-variable">content</span> <span class="hljs-operator">=</span> doc.select(<span class="hljs-string">&quot;.article-viewer p&quot;</span>);<br>            <span class="hljs-comment">// for (Element p : content) &#123;</span><br>            <span class="hljs-comment">//     p.select(&quot;img&quot;).remove();</span><br>            <span class="hljs-comment">//     p.select(&quot;a&quot;).remove();</span><br>            <span class="hljs-comment">// &#125;</span><br><br>            System.out.println(<span class="hljs-string">&quot;---------------博文正文------------------&quot;</span>);<br>            <span class="hljs-comment">// System.out.println(content);</span><br><br>            <span class="hljs-type">byte</span>[] contentBytes = content.toString().getBytes(StandardCharsets.UTF_8);<br><br>            System.out.println(contentBytes);<br>            <span class="hljs-comment">// 获取博文</span><br>            <span class="hljs-type">Article</span> <span class="hljs-variable">article</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Article</span>();<br>            article.setId(Long.valueOf(<span class="hljs-string">&quot;7313418992310976549&quot;</span>));<br>            article.setTitle(title.text());<br>            <br>            <span class="hljs-comment">// 直接保存</span><br>            article.setContent(content.toString());<br><br>            <span class="hljs-comment">// article.setContent(Arrays.toString(contentBytes));</span><br>            <span class="hljs-comment">// article.setContent(content.toString());</span><br>            article.setAuthorId(<span class="hljs-number">0L</span>);<br>            article.setView(<span class="hljs-number">0</span>);<br>            article.setLikes(<span class="hljs-number">0</span>);<br>            article.setComments(<span class="hljs-string">&quot;&quot;</span>);<br>            article.setCollects(<span class="hljs-number">0</span>);<br>            article.setTags(<span class="hljs-string">&quot;&quot;</span>);<br><br>            articleService.save(article);<br>        &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>        &#125;<br>    &#125;<br>article.setContent(contentBytes);<br></code></pre></td></tr></table></figure>

<p>这里直接保存，会出现字符编码无法识别而转换错误，就是因为保存的数据记录中有 emoji 这样的小图标</p>
<p>这里我也查询了相关文章，解决这个问题，虽然最后没有解决，但仍可做参考：</p>
<blockquote>
<p>🔥 推荐阅读：</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_32684617/article/details/93495460">用户昵称含emoji表情保存到数据库中报错SQLException: Incorrect string value: ‘\xF0\x9F\x91\xA7’ for colum n …-CSDN博客</a></li>
<li><a target="_blank" rel="noopener" href="https://bbs.csdn.net/topics/392268222">java后台接收获取微信昵称，昵称包含小图标保存到数据库报错-CSDN社区</a></li>
</ul>
</blockquote>
<figure class="highlight subunit"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs subunit"><span class="hljs-keyword">Error </span>updating database.  Cause: java.sql.SQLException: Incorrect string value: &#x27;\xF0\x9F\x8D\x84 \xE5...&#x27; for column &#x27;content&#x27; at row 1<br></code></pre></td></tr></table></figure>

<h4 id="转二进制数组保存"><a href="#转二进制数组保存" class="headerlink" title="转二进制数组保存"></a>转二进制数组保存</h4><p>保存数据的时候，先转码保存（转为<strong>二进制字符数组</strong>），取出数据的时候，解码后再使用：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"> <span class="hljs-type">byte</span>[] contentBytes = content.toString().getBytes(StandardCharsets.UTF_8);<br> <span class="hljs-comment">// 获取博文</span><br> <span class="hljs-type">Article</span> <span class="hljs-variable">article</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Article</span>();<br> article.setId(Long.valueOf(<span class="hljs-string">&quot;7313418992310976549&quot;</span>));<br> article.setTitle(title.text());<br> article.setContent(Arrays.toString(contentBytes));<br> article.setAuthorId(<span class="hljs-number">0L</span>);<br> article.setView(<span class="hljs-number">0</span>);<br> article.setLikes(<span class="hljs-number">0</span>);<br> article.setComments(<span class="hljs-string">&quot;&quot;</span>);<br> article.setCollects(<span class="hljs-number">0</span>);<br> article.setTags(<span class="hljs-string">&quot;&quot;</span>);<br><br>articleService.save(article);<br><br><span class="hljs-type">String</span> <span class="hljs-variable">decodedContent</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(contentBytes, StandardCharsets.UTF_8);<br>System.out.println(<span class="hljs-string">&quot;-------------解码后--------------&quot;</span>);<br>System.out.println(decodedContent);<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213223752469.png" alt="image-20240213223752469"></p>
<p>保存到数据库中的问题解决了，接下来就是保证正确从数据库中拿到数据并解码出原数据：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">String</span> <span class="hljs-variable">contentStr</span> <span class="hljs-operator">=</span> article.getContent();<br><span class="hljs-type">byte</span>[] contentBytes = contentStr.getBytes(StandardCharsets.UTF_8);<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 移除两端的方括号  </span><br><span class="hljs-type">String</span> <span class="hljs-variable">contentStrWithoutBrackets</span> <span class="hljs-operator">=</span> contentStr.substring(<span class="hljs-number">1</span>, contentStr.length() - <span class="hljs-number">1</span>);  <br><span class="hljs-comment">// 使用逗号作为分隔符分割字符串  </span><br>String[] byteStrs = contentStrWithoutBrackets.split(<span class="hljs-string">&quot;,&quot;</span>);  <br><span class="hljs-comment">// 将字符串数组转换为字节数组  </span><br><span class="hljs-type">byte</span>[] contentBytes = <span class="hljs-keyword">new</span> <span class="hljs-title class_">byte</span>[byteStrs.length];  <br><span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; byteStrs.length; i++) &#123;  <br>    contentBytes[i] = Byte.parseByte(byteStrs[i]);  <br>&#125;<br></code></pre></td></tr></table></figure>

<p>经尝试，以上方法并不能成功将保存到数据库中的二进制数组成功解码成原文章字符串，解码失败</p>
<p>我们保存二进制数组到数据库中，成功避免了 emoji 表情转码保存失败的问题，但是这样存入数据库，取出时就不好处理了</p>
<p>如上，对 byte [] 直接解码是可以获取原文内容 content 的，那就干脆直接保存 byte [] 到数据库中了，改变字段 content 属性为 blob：</p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231224104321929.png" alt="image-20231224104321929"></p>
<ul>
<li>改变对应实体类的字段数据类型为 byte []：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 文章内容</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> <span class="hljs-type">byte</span>[] content;<br></code></pre></td></tr></table></figure>

<ul>
<li>接下来，我们选择直接保存 byte [] 到数据库中即可：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">article.setContent(contentBytes);<br></code></pre></td></tr></table></figure>

<p>这里也可以看出，将 byte [] 转字符串数组后保存和直接保存 byte [] 到数据库中的形式是很不一样的（如下图所示）：</p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231224104532681.png" alt="image-20231224104532681"></p>
<ul>
<li>改变数据库字符集一点都不好使：<strong>（2023/12/23晚）</strong></li>
</ul>
<blockquote>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_32684617/article/details/93495460">用户昵称含emoji表情保存到数据库中报错SQLException: Incorrect string value: ‘\xF0\x9F\x91\xA7’ for colum n …-CSDN博客</a></p>
<p><a target="_blank" rel="noopener" href="https://bbs.csdn.net/topics/392268222">java后台接收获取微信昵称，昵称包含小图标保存到数据库报错-CSDN社区</a></p>
</blockquote>
<h4 id="直接保存二进制"><a href="#直接保存二进制" class="headerlink" title="直接保存二进制"></a>直接保存二进制</h4><p>终于解决了如何正确保存含 <code>emoji</code> 表情数据到数据库中的问题了</p>
<p>经过诸多尝试，仍无法正确解码保存 emoji 表情，经过测试，转码保存解决报错：<strong>（2023/12/24早）</strong></p>
<p>经过前面的测试发现，转码后保存 byte [] 可以解决编码错误，问题是出在保存数据库时</p>
<p>由于字段 content 为 text（varchar 也可以，可能会出现要保存的数据记录过长而导致溢出，就选择 text 了），所以我们在保存 byte [] 到数据库中时，是先转换成字符串再保存的</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">article.setContent(Arrays.toString(contentBytes));<br></code></pre></td></tr></table></figure>

<p>爬取文章，获取标题和内容：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">Document</span> <span class="hljs-variable">doc</span> <span class="hljs-operator">=</span> Jsoup.connect(url)<br>                    .userAgent(<span class="hljs-string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.81&quot;</span>)<br>                    .get();<br><br><br><span class="hljs-type">Elements</span> <span class="hljs-variable">title</span> <span class="hljs-operator">=</span> doc.select(<span class="hljs-string">&quot;.article-area .article-title&quot;</span>);<br>System.out.println(<span class="hljs-string">&quot;----------------博文标题----------------&quot;</span>);<br>System.out.println(title.text());<br><br><span class="hljs-type">Elements</span> <span class="hljs-variable">content</span> <span class="hljs-operator">=</span> doc.select(<span class="hljs-string">&quot;.article-viewer p&quot;</span>);<br><span class="hljs-comment">// for (Element p : content) &#123;</span><br><span class="hljs-comment">//     p.select(&quot;img&quot;).remove();</span><br><span class="hljs-comment">//     p.select(&quot;a&quot;).remove();</span><br><span class="hljs-comment">// &#125;</span><br><br>System.out.println(<span class="hljs-string">&quot;---------------博文正文------------------&quot;</span>);<br><span class="hljs-comment">// System.out.println(content);</span><br><br><span class="hljs-type">byte</span>[] contentBytes = content.toString().getBytes(StandardCharsets.UTF_8);<br></code></pre></td></tr></table></figure>

<p>将文章内容转二进制后，保存至数据库中：</p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213224057166.png" alt="image-20240213224057166"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 文章内容</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> <span class="hljs-type">byte</span>[] content;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 获取博文</span><br><span class="hljs-type">Article</span> <span class="hljs-variable">article</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Article</span>();<br>article.setTitle(title.text());<br>article.setContent(contentBytes);<br><span class="hljs-comment">// article.setContent(Arrays.toString(contentBytes));</span><br><span class="hljs-comment">// article.setContent(content.toString());</span><br>article.setAuthorId(<span class="hljs-number">0L</span>);<br>article.setView(<span class="hljs-number">0</span>);<br>article.setLikes(<span class="hljs-number">0</span>);<br>article.setComments(<span class="hljs-string">&quot;&quot;</span>);<br>article.setCollects(<span class="hljs-number">0</span>);<br>article.setTags(<span class="hljs-string">&quot;&quot;</span>);<br><br>articleService.save(article);<br></code></pre></td></tr></table></figure>

<p>如下，成功保存文章内容至 MySQL 数据库中：</p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213224145889.png" alt="image-20240213224145889"></p>
<p>如需获取文章数据，取出二进制数组后，是可以直接解码获取原文的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">String</span> <span class="hljs-variable">decodedContent</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(contentBytes, StandardCharsets.UTF_8);<br>            System.out.println(<span class="hljs-string">&quot;-------------解码后--------------&quot;</span>);<br>            System.out.println(decodedContent);<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231224104703715.png" alt="image-20231224104703715"></p>
<ul>
<li>接下来，就可以直接从数据库中取出数据并解码了：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">Article</span> <span class="hljs-variable">article</span> <span class="hljs-operator">=</span> articleService.getById(<span class="hljs-number">7313418992310976549L</span>);<br><br><span class="hljs-type">Long</span> <span class="hljs-variable">id</span> <span class="hljs-operator">=</span> article.getId();<br><span class="hljs-type">String</span> <span class="hljs-variable">title</span> <span class="hljs-operator">=</span> article.getTitle();<br><span class="hljs-type">byte</span>[] content = article.getContent();<br><br><span class="hljs-type">String</span> <span class="hljs-variable">decodedContent</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(content, StandardCharsets.UTF_8);<br><span class="hljs-type">Integer</span> <span class="hljs-variable">type</span> <span class="hljs-operator">=</span> article.getType();<br><br>System.out.println(id);<br>System.out.println(title);<br>System.out.println(decodedContent);<br>System.out.println(type);<br></code></pre></td></tr></table></figure>

<ul>
<li>解码结果如下：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231224104725599.png" alt="image-20231224104725599"></p>
<ul>
<li>至此，我们成功解决了如何正确保存数据记录到数据库中的问题，并成功解决了编码问题<strong>（2023/12/24午）</strong></li>
</ul>
<h3 id="前端后续"><a href="#前端后续" class="headerlink" title="前端后续"></a>前端后续</h3><ul>
<li>这么一改，前端报错了：</li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20231224164743797.png" alt="image-20231224164743797"></p>
<ul>
<li>原因是拿到的 content 字段值无法解析，那我们将实体类 ArticleDTO 的 content 属性类型仍设为 String：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 文章内容</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> String content;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">   * articleVO 为 articleVO</span><br><span class="hljs-comment">   *</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@param</span> article article</span><br><span class="hljs-comment">   * <span class="hljs-doctag">@return</span> articleVO</span><br><span class="hljs-comment">   */</span><br>  <span class="hljs-keyword">public</span> ArticleVO <span class="hljs-title function_">getArticleVOByArticle</span><span class="hljs-params">(Article article)</span> &#123;<br>      <span class="hljs-type">ArticleVO</span> <span class="hljs-variable">articleVO</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArticleVO</span>();<br><br>      articleVO.setId(article.getId());<br>      articleVO.setTitle(article.getTitle());<br>      articleVO.setDescription(article.getDescription());<br><br>      <span class="hljs-type">byte</span>[] contentBytes = article.getContent();<br>      <span class="hljs-type">String</span> <span class="hljs-variable">content</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(contentBytes, StandardCharsets.UTF_8);<br>      articleVO.setContent(content);<br>      <br>      ..........................<br></code></pre></td></tr></table></figure>

<h3 id="编码不一致-ES-同步失败"><a href="#编码不一致-ES-同步失败" class="headerlink" title="编码不一致 ES 同步失败"></a>编码不一致 ES 同步失败</h3><ul>
<li>他爷爷的，同步 MySQL 和 ES 数据的时候又出现了问题，我还得搞如何在 ES 中存储 byte [] 数据：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231224161918436.png" alt="image-20231224161918436"></p>
<ul>
<li>那就重新创建一个索引，能够使 content 字段存放二进制记录：<strong>（2023/12/24晚）</strong></li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs sql">PUT <span class="hljs-operator">/</span>article_v3<br>&#123;  <br>  &quot;mappings&quot;: &#123;<br>    &quot;properties&quot;: &#123;  <br>    &quot;title&quot;: &#123;  <br>      &quot;type&quot;: &quot;text&quot;,  <br>      &quot;analyzer&quot;: &quot;ik_max_word&quot;,  <br>      &quot;search_analyzer&quot;: &quot;ik_smart&quot;,  <br>      &quot;fields&quot;: &#123;  <br>        &quot;keyword&quot;: &#123;  <br>          &quot;type&quot;: &quot;keyword&quot;,  <br>          &quot;ignore_above&quot;: <span class="hljs-number">256</span>  <br>        &#125;,  <br>        &quot;suggest&quot;: &#123;  <br>          &quot;type&quot;: &quot;completion&quot;,  <br>          &quot;analyzer&quot;: &quot;ik_max_word&quot;  <br>        &#125;  <br>      &#125;  <br>    &#125;,  <br>    &quot;content&quot;: &#123;  <br>      &quot;type&quot;: &quot;binary&quot;,  <br>      &quot;fields&quot;: &#123;  <br>        &quot;suggest&quot;: &#123;  <br>          &quot;type&quot;: &quot;completion&quot;,  <br>          &quot;analyzer&quot;: &quot;ik_max_word&quot;  <br>        &#125;  <br>      &#125;  <br>    &#125;  <br>  &#125;  <br>  &#125; <br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>妈的，执行一次查询后，ES 又报错：</li>
</ul>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20231224170549612.png" alt="image-20231224170549612"></p>
<ul>
<li>这是什么问题？ES 中 article_v4 索引中的字段 content 是 <code>binary</code> 属性，而二进制数据是不支持 <code>match queries</code> 筛选操作的：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 按关键词检索 满足其一 √</span><br>       <span class="hljs-keyword">if</span> (StringUtils.isNotBlank(searchText)) &#123;<br>           boolQueryBuilder.should(QueryBuilders.matchQuery(<span class="hljs-string">&quot;title&quot;</span>, searchText));<br>           boolQueryBuilder.should(QueryBuilders.matchQuery(<span class="hljs-string">&quot;description&quot;</span>, searchText));<br>           boolQueryBuilder.should(QueryBuilders.matchQuery(<span class="hljs-string">&quot;content&quot;</span>, searchText));<br>           boolQueryBuilder.minimumShouldMatch(<span class="hljs-number">1</span>);<br>       &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>在编写 DSL 语句插入新索引时也不能对 binary 属性的字段添加 <code>analyzer</code> 和 <code>search_analyzer</code>：</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs sql">&quot;content&quot;: &#123;  <br>  &quot;type&quot;: &quot;binary&quot;,  <br>  &quot;analyzer&quot;: &quot;ik_max_word&quot;,  <br>  &quot;search_analyzer&quot;: &quot;ik_smart&quot;,  <br>  &quot;fields&quot;: &#123;  <br>    &quot;suggest&quot;: &#123;  <br>      &quot;type&quot;: &quot;completion&quot;,  <br>      &quot;analyzer&quot;: &quot;ik_max_word&quot;  <br>    &#125;  <br>  &#125;  <br>&#125; <br></code></pre></td></tr></table></figure>

<ul>
<li>那我不搞了，同步博文数据时，不同步 content 字段了，使用 ES 查询到 id，再拿 id 去查询数据库就行了</li>
</ul>
<h2 id="搜索建议-1"><a href="#搜索建议-1" class="headerlink" title="搜索建议"></a>搜索建议</h2><h3 id="后端-3"><a href="#后端-3" class="headerlink" title="后端"></a>后端</h3><ul>
<li>新增索引，特别指定 <code>title</code> 字段支持 <code>suggest</code> 搜索词建议：<strong>（2023/12/24晚）</strong></li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs sql">PUT <span class="hljs-operator">/</span>article_v4<br>&#123;  <br>  &quot;mappings&quot;: &#123;  <br>    &quot;properties&quot;: &#123;  <br>      &quot;title&quot;: &#123;  <br>        &quot;type&quot;: &quot;text&quot;,  <br>        &quot;analyzer&quot;: &quot;ik_max_word&quot;,  <br>        &quot;search_analyzer&quot;: &quot;ik_smart&quot;,  <br>        &quot;fields&quot;: &#123;  <br>          &quot;keyword&quot;: &#123;  <br>            &quot;type&quot;: &quot;keyword&quot;,  <br>            &quot;ignore_above&quot;: <span class="hljs-number">256</span>  <br>          &#125;,  <br>          &quot;suggest&quot;: &#123;    <br>            &quot;type&quot;: &quot;completion&quot;,   <br>            &quot;analyzer&quot;: &quot;ik_max_word&quot;    <br>          &#125;  <br>        &#125;  <br>      &#125;<br>    &#125;  <br>  &#125;  <br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>使用 <code>Logstash</code> 同步 <code>MySQL</code> 和 <code>ES</code>，该写数据同步配置文件（排除 <code>content</code> 字段），开启数据同步：</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">filter</span> &#123;<br>  mutate &#123;<br>    rename <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> &#123;<br>      &quot;updatetime&quot; <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> &quot;updateTime&quot;<br>      &quot;userid&quot; <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> &quot;userId&quot;<br>      &quot;createtime&quot; <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> &quot;createTime&quot;<br>      &quot;isdelete&quot; <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> &quot;isDelete&quot;<br>    &#125;<br>    remove_field <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> [&quot;thumbnum&quot;, &quot;favournum&quot;,&quot;content&quot;]<br>  &#125;<br>&#125;<br><br>output &#123;<br>  stdout &#123; codec <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> rubydebug &#125;<br>    elasticsearch &#123;  <br>    hosts <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> [&quot;127.0.0.1:9200&quot;]  <br>    index <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> &quot;article_v4&quot;  <br>    document_id <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> &quot;%&#123;id&#125;&quot;  <br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>配置搜索建议</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Resource</span><br>  <span class="hljs-keyword">private</span> ElasticsearchRestTemplate elasticsearchRestTemplate;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 搜索建议</span><br>        <span class="hljs-type">SuggestBuilder</span> <span class="hljs-variable">suggestBuilder</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">SuggestBuilder</span>()<br>                .addSuggestion(<span class="hljs-string">&quot;suggestionTitle&quot;</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">CompletionSuggestionBuilder</span>(<span class="hljs-string">&quot;title.suggest&quot;</span>).skipDuplicates(<span class="hljs-literal">true</span>).size(<span class="hljs-number">5</span>).prefix(searchText));<br></code></pre></td></tr></table></figure>

<ul>
<li>注意，这里的搜索建议配置跟这条 DSL 语句是对应的：</li>
</ul>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">GET</span> article_v4<span class="hljs-operator">/</span>_search<br>&#123;<br>  &quot;query&quot;: &#123;<br>    &quot;match&quot;: &#123; &quot;title&quot;: &quot;测试&quot; &#125;<br>  &#125;,<br>    &quot;highlight&quot;: &#123;<br>    &quot;fields&quot;: &#123;<br>      &quot;title&quot;: &#123;&#125;<br>    &#125;<br>  &#125;,<br>    &quot;suggest&quot;: &#123;<span class="hljs-operator">/</span><span class="hljs-operator">/</span>这个字段是关键字,不能随便起名<br>    &quot;my-suggest&quot;: &#123; <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 这个是自己起的名字<br>      &quot;prefix&quot;: &quot;测试&quot;, <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 这个是前缀<br>      &quot;completion&quot;: &#123;<br>        &quot;field&quot;: &quot;title.suggest&quot; <span class="hljs-operator">/</span><span class="hljs-operator">/</span>这个是你自己定义的索引<br>      &#125;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>构造查询</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 分页</span><br>    <span class="hljs-type">PageRequest</span> <span class="hljs-variable">pageRequest</span> <span class="hljs-operator">=</span> PageRequest.of((<span class="hljs-type">int</span>) pageNum, (<span class="hljs-type">int</span>) pageSize);<br>    <span class="hljs-comment">// 构造查询</span><br>    <span class="hljs-type">NativeSearchQuery</span> <span class="hljs-variable">searchQuery</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NativeSearchQueryBuilder</span>()<br>            .withQuery(boolQueryBuilder)<br>            .withHighlightBuilder(highlightBuilder)<br>            .withPageable(pageRequest)<br>            .withSorts(sortBuilder)<br>            .withSuggestBuilder(suggestBuilder)<br>            .build();<br>    SearchHits&lt;ArticleEsDTO&gt; searchHits = elasticsearchRestTemplate.search(searchQuery, ArticleEsDTO.class);<br></code></pre></td></tr></table></figure>

<ul>
<li>根据搜索词 <code>searchText</code> 从 <code>SearchResponse</code> 中获取建议：</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 从 SearchResponse 中获取建议</span><br>     <span class="hljs-type">Suggest</span> <span class="hljs-variable">suggest</span> <span class="hljs-operator">=</span> searchHits.getSuggest();<br>     <span class="hljs-keyword">if</span> (suggest != <span class="hljs-literal">null</span>) &#123;<br>         <span class="hljs-comment">// 获取特定的建议结果</span><br>         Suggest.Suggestion&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Suggest</span>.Suggestion.Entry&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Suggest</span>.Suggestion.Entry.Option&gt;&gt; termSuggestion = suggest.getSuggestion(<span class="hljs-string">&quot;suggestionTitle&quot;</span>);<br>         <span class="hljs-keyword">if</span> (termSuggestion != <span class="hljs-literal">null</span>) &#123;<br>             <span class="hljs-comment">// 遍历建议选项</span><br>             <span class="hljs-keyword">for</span> (Suggest.Suggestion.Entry&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Suggest</span>.Suggestion.Entry.Option&gt; entry : termSuggestion.getEntries()) &#123;<br>                 <span class="hljs-comment">// 处理每个建议选项，例如打印它们</span><br>                 System.out.println(entry.getText());<br>                 System.out.println(entry.getOptions().get(<span class="hljs-number">0</span>).getText());<br>                 System.out.println(entry.getOptions().get(<span class="hljs-number">1</span>).getText());<br>             &#125;<br>         &#125;<br>     &#125;<br></code></pre></td></tr></table></figure>

<ul>
<li>注意看，这里可以选取的<code>建议选项</code>跟这里是一一对应的：</li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231224221008892.png" alt="image-20231224221008892"></p>
<ul>
<li>执行查询，成功获取到搜索关键词对应的查询建议：<strong>（2023/12/24晚）</strong></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231224221206525.png" alt="image-20231224221206525"></p>
<h3 id="前端-3"><a href="#前端-3" class="headerlink" title="前端"></a>前端</h3><ul>
<li>自动完成输入框<strong>（2023/12/25午）</strong></li>
</ul>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs tsx">&lt;a-auto-complete<br>        v-<span class="hljs-attr">model</span>:value=<span class="hljs-string">&quot;searchText&quot;</span><br>        :options=<span class="hljs-string">&quot;options&quot;</span><br>        style=<span class="hljs-string">&quot;width: 200px&quot;</span><br>        placeholder=<span class="hljs-string">&quot;请输入搜索关键词&quot;</span><br>        <span class="hljs-meta">@select</span>=<span class="hljs-string">&quot;onSelect&quot;</span><br>        <span class="hljs-meta">@search</span>=<span class="hljs-string">&quot;onSearch&quot;</span><br>    /&gt;     <span class="hljs-comment">// 搜索建议</span><br></code></pre></td></tr></table></figure>

<ul>
<li>发起请求，获取搜索建议词，保存返回结果并解析<strong>（2023/12/27早）</strong></li>
</ul>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// 搜索建议词</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">onSearchSuggest</span> = (<span class="hljs-params">suggestText: <span class="hljs-built_in">string</span></span>) =&gt; &#123;<br>  myAxios.<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;/search/suggest&quot;</span>, &#123;<br>    <span class="hljs-attr">params</span>: &#123;<br>      <span class="hljs-attr">suggestText</span>: suggestText,<br>    &#125;<br>  &#125;).<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">res: <span class="hljs-built_in">any</span></span>) =&gt;</span> &#123;<br>    <span class="hljs-comment">// console.log(&quot;res = &quot; + res)</span><br><br>    <span class="hljs-comment">// 搜索建议词不为空</span><br>    <span class="hljs-keyword">if</span> (res) &#123;<br>      suggestionList.<span class="hljs-property">value</span> = res<br>      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;res = &quot;</span> + suggestionList.<span class="hljs-property">value</span>)<br>    &#125;<br>  &#125;)<br><br>  options.<span class="hljs-property">value</span> = !suggestText<br>      ? []<br>      : [<span class="hljs-title function_">getSuggest</span>(suggestionList.<span class="hljs-property">value</span>[<span class="hljs-number">0</span>]), <span class="hljs-title function_">getSuggest</span>(suggestionList.<span class="hljs-property">value</span>[<span class="hljs-number">1</span>]),<br>        <span class="hljs-title function_">getSuggest</span>(suggestionList.<span class="hljs-property">value</span>[<span class="hljs-number">2</span>]), <span class="hljs-title function_">getSuggest</span>(suggestionList.<span class="hljs-property">value</span>[<span class="hljs-number">3</span>]), <span class="hljs-title function_">getSuggest</span>(suggestionList.<span class="hljs-property">value</span>[<span class="hljs-number">4</span>])];<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>绑定搜索建议词到<code>a-auto-complete</code>中，展示搜索建议</li>
</ul>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">Suggest</span> &#123;<br>  <span class="hljs-attr">value</span>: <span class="hljs-built_in">string</span>;<br>&#125;<br><br><span class="hljs-comment">// 请求获得建议词</span><br><span class="hljs-keyword">const</span> getSuggest = (<span class="hljs-attr">sug</span>:<span class="hljs-built_in">string</span>): <span class="hljs-function"><span class="hljs-params">Suggest</span> =&gt;</span> &#123;<br>  <span class="hljs-keyword">return</span> &#123;<br>    <span class="hljs-attr">value</span>: sug,<br>  &#125;;<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>支持从搜索建议列表中，单击选择其中一项填充到<code>a-auto-complete</code>输入框中</li>
</ul>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// 建议列表</span><br><span class="hljs-keyword">const</span> options = ref&lt;<span class="hljs-title class_">Suggest</span>[]&gt;([]);<br><br><span class="hljs-comment">// 从建议列表中选择一项</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">onSelect</span> = (<span class="hljs-params">value: <span class="hljs-built_in">string</span></span>) =&gt; &#123;<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;onSelect&#x27;</span>, value);<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>根据搜索关键词，执行搜索，获取并解析搜索结果</li>
</ul>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// 执行搜索</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">onSearch</span> = (<span class="hljs-params">searchText: <span class="hljs-built_in">string</span></span>) =&gt; &#123;<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(searchText)<br>  router.<span class="hljs-title function_">push</span>(&#123;<br>    <span class="hljs-attr">query</span>: &#123;<br>      ...searchParams.<span class="hljs-property">value</span>,<br>      <span class="hljs-attr">text</span>: searchText,<br>    &#125;,<br>  &#125;);<br><br>  <span class="hljs-title function_">loadData</span>(searchParams.<span class="hljs-property">value</span>);<br>  <span class="hljs-keyword">const</span> <span class="hljs-keyword">type</span> = route.<span class="hljs-property">params</span>.<span class="hljs-property">category</span>;<br><br>  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;post&quot;</span>) &#123;<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功检索出您感兴趣的诗词~&quot;</span>)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;user&quot;</span>) &#123;<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功检索出您感兴趣的用户~&quot;</span>)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;picture&quot;</span>) &#123;<br>    pictureList.<span class="hljs-property">value</span> = [];<br>    <span class="hljs-title function_">getImages</span>()<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;article&quot;</span>) &#123;<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功检索出您感兴趣的博文~&quot;</span>)<br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>目前实现的效果如下：<strong>（2023/12/25午）</strong></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231225001413628.png" alt="image-20231225001413628"></p>
<ul>
<li>在输入框中输入内容，即可显示建议列表</li>
<li>成功实现获取搜索建议词，实现代码已经在上述代码中更新，最终效果如下：<strong>（2023/12/27早）</strong></li>
</ul>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231227105751531.png" alt="image-20231227105751531"></p>
<h2 id="onSearch-函数未传参引发的问题"><a href="#onSearch-函数未传参引发的问题" class="headerlink" title="onSearch 函数未传参引发的问题"></a><code>onSearch</code> 函数未传参引发的问题</h2><ul>
<li>又解决了一个小问题：<strong>（2023/12/25晚）</strong></li>
<li>将之前的搜索框拆分成两个：<strong>自动完成输入框</strong>和一个 <code>Button</code>按钮：</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;!--搜索框--&gt;<br>&lt;a-input-search<br>    v-model:value=&quot;searchText&quot;<br>    placeholder=&quot;请输入搜索关键词&quot;<br>    enter-button=&quot;搜索&quot;<br>    size=&quot;large&quot;<br>    @search=&quot;onSearch&quot;<br>/&gt;<br></code></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-auto-complete<br>    v-model:value=&quot;searchText&quot;<br>    :options=&quot;options&quot;<br>    style=&quot;width: 200px&quot;<br>    placeholder=&quot;请输入搜索关键词&quot;<br>    @select=&quot;onSelect&quot;<br>    @search=&quot;onSearchSuggest&quot;<br>/&gt;<br><br>&lt;a-button type=&quot;primary&quot; @click=&quot;onSearch(searchText)&quot;&gt;搜索框&lt;/a-button&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li>这两个搜索函数分别执行了<code>搜索建议词搜索</code>和<code>数据库记录搜索</code>：</li>
</ul>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// 搜索建议词</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">onSearchSuggest</span> = (<span class="hljs-params">suggestText: <span class="hljs-built_in">string</span></span>) =&gt; &#123;<br>  myAxios.<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;/search/suggest&quot;</span>, &#123;<br>    <span class="hljs-attr">params</span>: &#123;<br>      <span class="hljs-attr">suggestText</span>: suggestText,<br>    &#125;<br>  &#125;).<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">res: <span class="hljs-built_in">any</span></span>) =&gt;</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;res = &quot;</span> + res)<br>  &#125;)<br><br>  options.<span class="hljs-property">value</span> = !suggestText<br>      ? []<br>      : [<span class="hljs-title function_">getSuggest</span>(), <span class="hljs-title function_">getSuggest</span>(), <span class="hljs-title function_">getSuggest</span>()];<br>&#125;;<br></code></pre></td></tr></table></figure>

<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// 执行搜索</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">onSearch</span> = (<span class="hljs-params">searchText: <span class="hljs-built_in">string</span></span>) =&gt; &#123;<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(searchText)<br>  router.<span class="hljs-title function_">push</span>(&#123;<br>    <span class="hljs-attr">query</span>: &#123;<br>      ...searchParams.<span class="hljs-property">value</span>,<br>      <span class="hljs-attr">text</span>: searchText,<br>    &#125;,<br>  &#125;);<br><br>  <span class="hljs-title function_">loadData</span>(searchParams.<span class="hljs-property">value</span>);<br>  <span class="hljs-keyword">const</span> <span class="hljs-keyword">type</span> = route.<span class="hljs-property">params</span>.<span class="hljs-property">category</span>;<br><br>  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;post&quot;</span>) &#123;<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功检索出您感兴趣的诗词~&quot;</span>)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;user&quot;</span>) &#123;<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功检索出您感兴趣的用户~&quot;</span>)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;picture&quot;</span>) &#123;<br>    pictureList.<span class="hljs-property">value</span> = [];<br>    <span class="hljs-title function_">getImages</span>()<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">&quot;article&quot;</span>) &#123;<br>    message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&quot;成功检索出您感兴趣的博文~&quot;</span>)<br>  &#125;<br>&#125;;<br></code></pre></td></tr></table></figure>

<ul>
<li>结果就是因为转换成使用一个 Button 那按钮执行搜索操作，没有及时改正语法格式，写成了：</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-button type=&quot;primary&quot; @click=&quot;onSearch&quot;&gt;搜索框&lt;/a-button&gt;<br></code></pre></td></tr></table></figure>

<ul>
<li>显而易见，<code>onSearch</code> 函数并没有传递参数，这就导致了 ·onSearch· 函数一直不会被正确执行</li>
<li>还好我比较聪明，发现了这个问题<strong>（2023/12/25晚）</strong></li>
</ul>
<h2 id="热搜词统计"><a href="#热搜词统计" class="headerlink" title="热搜词统计"></a>热搜词统计</h2><p><a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_29752857/article/details/132169845">RedisTemplate.opsForZSet()用法简介并举例-CSDN博客</a></p>
<p>思路一：</p>
<p>key 值存储搜索词条，value 值用 String 数据结构，存储：<strong>用户 id + 搜索次数 + 搜索时间</strong>，方便根据搜索词条查询词条相关信息：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br>   <span class="hljs-keyword">public</span> List&lt;Message&gt; <span class="hljs-title function_">setHotWords</span><span class="hljs-params">(String suggestTextStr, HttpServletRequest request)</span> &#123;<br>       <span class="hljs-comment">// 获取当前登录用户</span><br>       <span class="hljs-comment">// User loginUser = (User) request.getSession().getAttribute(USER_LOGIN_STATE);</span><br>       <span class="hljs-comment">//</span><br>       <span class="hljs-comment">// Long userId = loginUser.getId();</span><br><br>       <span class="hljs-keyword">if</span> (com.qcloud.cos.utils.StringUtils.isNullOrEmpty(suggestTextStr)) &#123;<br>           <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>       &#125;<br><br>       <span class="hljs-comment">// 按空格，获取每个搜索词</span><br>       String[] suggestTexts = suggestTextStr.split(<span class="hljs-string">&quot; &quot;</span>);<br><br>       ArrayList&lt;Message&gt; messageList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>       <span class="hljs-keyword">for</span> (String suggestText : suggestTexts) &#123;<br>           <span class="hljs-comment">// 从 Redis 中获取原始消息</span><br>           <span class="hljs-type">String</span> <span class="hljs-variable">messageStr</span> <span class="hljs-operator">=</span> redisTemplate.opsForValue().get(String.format(REDIS_KEY_TEMPLATE, suggestText));<br><br>           <span class="hljs-type">Message</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>           <span class="hljs-type">Gson</span> <span class="hljs-variable">gson</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br>           <span class="hljs-comment">// 检查原始消息是否为空</span><br>           <span class="hljs-keyword">if</span> (ObjectUtils.isNotEmpty(messageStr)) &#123;<br>               <span class="hljs-comment">// 获取原始消息中的 searchNum 字段并增加 1</span><br>               <span class="hljs-type">Message</span> <span class="hljs-variable">mes</span> <span class="hljs-operator">=</span> gson.fromJson(messageStr, Message.class);<br>               <span class="hljs-type">Integer</span> <span class="hljs-variable">searchNum</span> <span class="hljs-operator">=</span> mes.getSearchNum();<br>               <span class="hljs-type">Integer</span> <span class="hljs-variable">newSearchNum</span> <span class="hljs-operator">=</span> searchNum + <span class="hljs-number">1</span>;<br><br>               <span class="hljs-comment">// 创建新的 Message 对象</span><br>               message = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Message</span>(MESSAGE_ID, suggestText, newSearchNum);<br>           &#125; <span class="hljs-keyword">else</span> &#123;<br>               <span class="hljs-comment">// 创建新的 Message 对象</span><br>               message = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Message</span>(MESSAGE_ID, suggestText, SEARCH_NUM);<br>           &#125;<br>           <span class="hljs-type">boolean</span> <span class="hljs-variable">add</span> <span class="hljs-operator">=</span> messageList.add(message);<br>           ThrowUtils.throwIf(!add, ErrorCode.OPERATION_ERROR, <span class="hljs-string">&quot;记录热搜词失败&quot;</span>);<br><br>           <span class="hljs-comment">// 将新的 Message 对象存回 Redis 中，设置过期时间为 30 天</span><br>           redisTemplate.opsForValue()<br>                    .set(String.format(REDIS_KEY_TEMPLATE, suggestText), gson.toJson(message), <span class="hljs-number">30</span>, TimeUnit.DAYS);<br>       &#125;<br><br>       <span class="hljs-keyword">return</span> messageList;<br>   &#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240204002441626.png" alt="image-20240204002441626"></p>
<p>很方便查询所有<strong>搜索词条相关信息</strong>，但根据 value 值内的 <code>searchNum</code> 按序查询<strong>前十条数据</strong>较困难：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> List&lt;Message&gt; <span class="hljs-title function_">getHotWords</span><span class="hljs-params">()</span> &#123;<br>    Set&lt;String&gt; keys = redisTemplate.keys(<span class="hljs-string">&quot;search:hot*&quot;</span>);<br><br>    ArrayList&lt;Message&gt; hotWordList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>    <span class="hljs-type">Gson</span> <span class="hljs-variable">gson</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br>    <span class="hljs-keyword">for</span> (String key : Objects.requireNonNull(keys)) &#123;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">messageStr</span> <span class="hljs-operator">=</span> redisTemplate.opsForValue().get(key);<br>        <span class="hljs-type">Message</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> gson.fromJson(messageStr, Message.class);<br>        hotWordList.add(message);<br>    &#125;<br><br>    System.out.println(keys);<br>    <span class="hljs-keyword">return</span> hotWordList;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>词条相关信息：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Message</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 搜索词条</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Long userId;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 搜索词条</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> String searchText;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 搜索词条</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Integer searchNum;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Message</span><span class="hljs-params">(Long userId, String searchText, Integer searchNum)</span> &#123;<br>        <span class="hljs-built_in">this</span>.userId = userId;<br>        <span class="hljs-built_in">this</span>.searchText = searchText;<br>        <span class="hljs-built_in">this</span>.searchNum = searchNum;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Message</span><span class="hljs-params">(Long userId, Integer searchNum)</span> &#123;<br>        <span class="hljs-built_in">this</span>.userId = userId;<br>        <span class="hljs-built_in">this</span>.searchNum = searchNum;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>思路二：</p>
<p>使用 ZSet 数据结构，key 值存储搜索时间，可统计不同时间段内的热门搜索词</p>
<p>value 值存储：<strong>用户 id + 搜索次数 + 搜索时间</strong>，维护 score 分数，更新搜索词条<code>searchNum</code>，即更新<code>score</code>分数：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> List&lt;Message&gt; <span class="hljs-title function_">setHotWords</span><span class="hljs-params">(String searchTextStr, HttpServletRequest request)</span> &#123;<br>    <span class="hljs-comment">// 获取当前登录用户</span><br>    <span class="hljs-comment">// User loginUser = (User) request.getSession().getAttribute(USER_LOGIN_STATE);</span><br>    <span class="hljs-comment">//</span><br>    <span class="hljs-comment">// Long userId = loginUser.getId();</span><br><br>    <span class="hljs-keyword">if</span> (com.qcloud.cos.utils.StringUtils.isNullOrEmpty(searchTextStr)) &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>    &#125;<br><br>    <span class="hljs-comment">// 按空格，获取每个搜索词</span><br>    String[] searchTexts = searchTextStr.split(<span class="hljs-string">&quot; &quot;</span>);<br><br>    ArrayList&lt;Message&gt; messageList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>    <span class="hljs-keyword">for</span> (String searchText : searchTexts) &#123;<br>        <span class="hljs-comment">// 从 Redis 中获取原始消息</span><br>        <span class="hljs-type">Long</span> <span class="hljs-variable">size</span> <span class="hljs-operator">=</span> redisTemplate.opsForZSet()<br>                .zCard(String.format(REDIS_KEY_TEMPLATE, SEARCH_TIME));<br><br>        <span class="hljs-type">Message</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>        <span class="hljs-type">Gson</span> <span class="hljs-variable">gson</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br>        <span class="hljs-comment">// 原始消息不为空</span><br>        <span class="hljs-keyword">if</span> (size &gt; <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-comment">// 获取原始消息中的 searchNum 字段并增加 1</span><br>            <span class="hljs-type">Message</span> <span class="hljs-variable">mes</span> <span class="hljs-operator">=</span> gson.fromJson(SEARCH_TIME, Message.class);<br>            <span class="hljs-type">Double</span> <span class="hljs-variable">searchNum</span> <span class="hljs-operator">=</span> mes.getSearchNum();<br><br>            <span class="hljs-comment">// 创建新的 Message 对象</span><br>            message = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Message</span>(MESSAGE_ID, searchText, ++searchNum);<br>            <span class="hljs-comment">// 存入 Redis，更新 score</span><br>            redisTemplate.opsForZSet()<br>                    .incrementScore(String.format(REDIS_KEY_TEMPLATE, SEARCH_TIME), gson.toJson(message), <span class="hljs-number">1</span>);<br><br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-comment">// 创建新的 Message 对象</span><br>            message = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Message</span>(MESSAGE_ID, searchText, SEARCH_NUM);<br><br>            <span class="hljs-comment">// 将新的 Message 对象存回 Redis 中</span><br>            redisTemplate.opsForZSet()<br>                    .add(String.format(REDIS_KEY_TEMPLATE, SEARCH_TIME), gson.toJson(message), SEARCH_NUM);<br>        &#125;<br>        <span class="hljs-type">boolean</span> <span class="hljs-variable">add</span> <span class="hljs-operator">=</span> messageList.add(message);<br>        ThrowUtils.throwIf(!add, ErrorCode.OPERATION_ERROR, <span class="hljs-string">&quot;记录热搜词失败&quot;</span>);<br>    &#125;<br><br>    <span class="hljs-keyword">return</span> messageList;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>根据 <code>score</code> 搜索次数，十分便捷地查询出前十条词条信息，即<strong>热门搜索词条</strong>：<strong>（2024/02/05）</strong></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240204213220142.png" alt="image-20240204213220142"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240204213224414.png" alt="image-20240204213224414"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br>   <span class="hljs-keyword">public</span> List&lt;MessageVO&gt; <span class="hljs-title function_">getHotWords</span><span class="hljs-params">()</span> &#123;<br>       <span class="hljs-comment">// 根据 score 获取前十条搜索词条</span><br>       Set&lt;String&gt; messageSet = redisTemplate.opsForZSet()<br>               .range(String.format(REDIS_KEY_TEMPLATE, SEARCH_TIME), <span class="hljs-number">0</span>, <span class="hljs-number">9</span>);<br><br>       ArrayList&lt;MessageVO&gt; messageVOList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>       <span class="hljs-type">Gson</span> <span class="hljs-variable">gson</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Gson</span>();<br>       <span class="hljs-keyword">for</span> (String messageStr : Objects.requireNonNull(messageSet)) &#123;<br>           <span class="hljs-type">Message</span> <span class="hljs-variable">message</span> <span class="hljs-operator">=</span> gson.fromJson(messageStr, Message.class);<br>           <span class="hljs-comment">// 查询搜索词条对应 score</span><br>           <span class="hljs-type">Double</span> <span class="hljs-variable">score</span> <span class="hljs-operator">=</span> redisTemplate.opsForZSet()<br>                   .score(String.format(REDIS_KEY_TEMPLATE, SEARCH_TIME), messageStr);<br>           <span class="hljs-comment">// 封装 messageVO</span><br>           <span class="hljs-type">MessageVO</span> <span class="hljs-variable">messageVO</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageVO</span>();<br>           BeanUtils.copyProperties(message, messageVO);<br>           messageVO.setSearchNum(score);<br><br>           <span class="hljs-type">boolean</span> <span class="hljs-variable">add</span> <span class="hljs-operator">=</span> messageVOList.add(messageVO);<br>           ThrowUtils.throwIf(!add, ErrorCode.OPERATION_ERROR, <span class="hljs-string">&quot;获取热搜词失败&quot;</span>);<br>       &#125;<br><br>       <span class="hljs-keyword">return</span> messageVOList;<br>   &#125;<br></code></pre></td></tr></table></figure>

<p>词条相关信息：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Message</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 用户</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Long userId;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 搜索词条</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> String searchText;<br><br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Message</span><span class="hljs-params">(Long userId, String searchText)</span> &#123;<br>        <span class="hljs-built_in">this</span>.userId = userId;<br>        <span class="hljs-built_in">this</span>.searchText = searchText;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MessageVO</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 用户</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Long userId;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 搜索词条</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> String searchText;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 搜索词条</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Double searchNum;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">MessageVO</span><span class="hljs-params">()</span> &#123;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">MessageVO</span><span class="hljs-params">(Long userId, String searchText, Double searchNum)</span> &#123;<br>        <span class="hljs-built_in">this</span>.userId = userId;<br>        <span class="hljs-built_in">this</span>.searchText = searchText;<br>        <span class="hljs-built_in">this</span>.searchNum = searchNum;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>



<h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>全局请求过滤器，ThreadLocal 记录登录用户信息</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 4.需要处理的用户的请求,则判断登录状态,如果已经登录,则直接放行</span><br>Long userId;<br><span class="hljs-type">User</span> <span class="hljs-variable">currentUser</span> <span class="hljs-operator">=</span> (User) request.getSession().getAttribute(USER_LOGIN_STATE);<br><br><span class="hljs-keyword">if</span> (currentUser != <span class="hljs-literal">null</span>) &#123;<span class="hljs-comment">// Session中存储着用户id(登录成功)</span><br>    userId = currentUser.getId();<br>    log.info(<span class="hljs-string">&quot;该用户已登录,id为&#123;&#125;&quot;</span>, userId);<br><br>    BaseContext.setCurrentId(userId);<span class="hljs-comment">// ThreadLocal</span><br><br>    filterChain.doFilter(request, response);<span class="hljs-comment">// 放行请求</span><br>    <span class="hljs-keyword">return</span>;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>查询文章，携带查询用户信息</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">Long</span> <span class="hljs-variable">currentId</span> <span class="hljs-operator">=</span> BaseContext.getCurrentId();<br><span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> userService.getById(currentId);<br><span class="hljs-type">UserVO</span> <span class="hljs-variable">userVO</span> <span class="hljs-operator">=</span> userService.getUserVO(user);<br>articleVO.setUserVO(userVO);<br></code></pre></td></tr></table></figure>

<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20240205223255475.png" alt="image-20240205223255475"></p>
<p>成功查询到用户信息</p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240205223302449.png" alt="image-20240205223302449"></p>
<h2 id="网站首页"><a href="#网站首页" class="headerlink" title="网站首页"></a>网站首页</h2><p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240206132159098.png" alt="image-20240206132159098"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240206224443746.png" alt="image-20240206224443746"></p>
<p><a target="_blank" rel="noopener" href="https://theme-hope.vuejs.press/zh/">主页 | vuepress-theme-hope (vuejs.press)</a></p>
<p>关于该主题的使用方法，在此简单予以说明：</p>
<p>首页配置：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash">---<br>home: <span class="hljs-literal">true</span><br>icon: home<br>title: 项目主页<br>heroImage: /logo.svg<br>bgImage: /img/winter2.jpg<br>bgImageDark: https://theme-hope-assets.vuejs.press/bg/6-dark.svg<br>bgImageStyle:<br>  background-attachment: fixed<br>heroText: MemorySearch 忆搜阁<br>tagline: 一个强大的、内容丰富的聚合搜索中台<br></code></pre></td></tr></table></figure>



<h2 id="注册用户管理"><a href="#注册用户管理" class="headerlink" title="注册用户管理"></a>注册用户管理</h2><p>可算坑死我了：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs ts">&lt;a-table<br>          :columns=<span class="hljs-string">&quot;userColumns&quot;</span><br>          :data=<span class="hljs-string">&quot;userList ? userList : []&quot;</span><br>          :bordered=<span class="hljs-string">&quot;true&quot;</span><br>          :hoverable=<span class="hljs-string">&quot;true&quot;</span><br>          :stripe=<span class="hljs-string">&quot;true&quot;</span><br>          :show-header=<span class="hljs-string">&quot;true&quot;</span><br>          :pagination=<span class="hljs-string">&quot;pagination&quot;</span><br>      /&gt;<br></code></pre></td></tr></table></figure>

<p>千万不能写成： :data=”:data=”userList ? [] : userList”，完全低级错误，不好发现</p>
<h2 id="图片缓存实现"><a href="#图片缓存实现" class="headerlink" title="图片缓存实现"></a>图片缓存实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// Redis 保存搜索图片 两小时</span><br>  redisTemplate.opsForValue()<br>          .set(String.format(SEARCH_TEXT_KEY, String.valueOf(currentId)),<br>                  GSON.toJson(pictureList), <span class="hljs-number">2</span>, TimeUnit.HOURS);<br><br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240207191513830.png" alt="image-20240207191513830"></p>
<p>Open API 生成请求接口，还经过 Spring 过滤器：</p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">openapi --input http:<span class="hljs-regexp">//</span>localhost:<span class="hljs-number">8104</span><span class="hljs-regexp">/api/</span>v2<span class="hljs-regexp">/api-docs?group=memory-search --output ./g</span>enerated --client axios<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240207195455834.png" alt="image-20240207195455834"></p>
<h2 id="热门词分析"><a href="#热门词分析" class="headerlink" title="热门词分析"></a>热门词分析</h2><p>官方文档：[Aggregations | Elasticsearch Guide <a target="_blank" rel="noopener" href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-aggregations.html">7.17] | Elastic</a></p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">GET</span> <span class="hljs-operator">/</span>my<span class="hljs-operator">-</span>index<span class="hljs-number">-000001</span><span class="hljs-operator">/</span>_search<br>&#123;<br>  &quot;aggs&quot;: &#123;<br>    &quot;my-first-agg-name&quot;: &#123;<br>      &quot;terms&quot;: &#123;<br>        &quot;field&quot;: &quot;my-field&quot;<br>      &#125;<br>    &#125;,<br>    &quot;my-second-agg-name&quot;: &#123;<br>      &quot;avg&quot;: &#123;<br>        &quot;field&quot;: &quot;my-other-field&quot;<br>      &#125;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240214194406065.png" alt="image-20240214194406065"></p>
<p>如上，分别根据 title 和 description 字段分组聚合查询出热门话题，这样在代码中就需要处理多个</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">Map&lt;String, Aggregation&gt; asMap = aggregations1.getAsMap();<br>       System.out.println(asMap);<br>       <span class="hljs-type">Aggregation</span> <span class="hljs-variable">terms</span> <span class="hljs-operator">=</span> asMap.get(<span class="hljs-string">&quot;search_terms&quot;</span>);<br>       <span class="hljs-type">String</span> <span class="hljs-variable">name</span> <span class="hljs-operator">=</span> terms.getName();<br>       <span class="hljs-type">String</span> <span class="hljs-variable">type</span> <span class="hljs-operator">=</span> terms.getType();<br>       Map&lt;String, Object&gt; metadata = terms.getMetadata();<br><br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 添加聚合条件</span><br><span class="hljs-type">TermsAggregationBuilder</span> <span class="hljs-variable">search_terms</span> <span class="hljs-operator">=</span> AggregationBuilders.terms(<span class="hljs-string">&quot;search_terms&quot;</span>).field(<span class="hljs-string">&quot;message&quot;</span>).size(<span class="hljs-number">10</span>);<br><br><span class="hljs-comment">// 创建查询</span><br><span class="hljs-type">NativeSearchQuery</span> <span class="hljs-variable">searchQuery</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NativeSearchQueryBuilder</span>()<br>        .withAggregations(search_terms)   <span class="hljs-comment">// 添加聚合条件</span><br>        .build();<br><br><span class="hljs-comment">// 执行搜索，获取搜索结果</span><br>SearchHits&lt;MessageTest&gt; searchHits = elasticsearchRestTemplate.search(searchQuery, MessageTest.class);<br><br><span class="hljs-comment">// 获取Spring Data Elasticsearch的聚合结果</span><br><span class="hljs-type">ElasticsearchAggregations</span> <span class="hljs-variable">searchHitsAggregations</span> <span class="hljs-operator">=</span> (ElasticsearchAggregations) searchHits.getAggregations();<br><span class="hljs-type">Aggregations</span> <span class="hljs-variable">aggregations</span> <span class="hljs-operator">=</span> Objects.requireNonNull(searchHitsAggregations).aggregations();<br>List&lt;Aggregation&gt; aggregationList = aggregations.asList();<br><span class="hljs-keyword">for</span> (Aggregation aggregation : aggregationList) &#123;<br>    System.out.println(aggregation);<br>    aggregation.getName();<br>    <span class="hljs-comment">// 检查聚合类型并转换为相应的聚合对象</span><br>    <span class="hljs-keyword">if</span> (aggregation <span class="hljs-keyword">instanceof</span> Terms) &#123;<br>        <span class="hljs-type">Terms</span> <span class="hljs-variable">termsAggregation</span> <span class="hljs-operator">=</span> (Terms) aggregation;<br><br>        <span class="hljs-comment">// 获取buckets</span><br>        List&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Terms</span>.Bucket&gt; buckets = termsAggregation.getBuckets();<br><br>        <span class="hljs-comment">// 遍历buckets</span><br>        <span class="hljs-keyword">for</span> (Terms.Bucket bucket : buckets) &#123;<br>            System.out.println(<span class="hljs-string">&quot;Bucket Key: &quot;</span> + bucket.getKeyAsString());<br>            System.out.println(<span class="hljs-string">&quot;Bucket Doc Count: &quot;</span> + bucket.getDocCount());<br>            <span class="hljs-comment">// 如果需要，还可以获取其他bucket信息，如聚合的子聚合等</span><br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240211233736902.png" alt="image-20240211233736902"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240211233800313.png" alt="image-20240211233800313"></p>
<h2 id="诗词抓取"><a href="#诗词抓取" class="headerlink" title="诗词抓取"></a>诗词抓取</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">testFetchPoemByOnce</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>    <span class="hljs-comment">// new一个StopWatch对象</span><br>    <span class="hljs-type">StopWatch</span> <span class="hljs-variable">stopWatch</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StopWatch</span>();<br>    <span class="hljs-comment">// 计时开始</span><br>    stopWatch.start();<br><br>    <span class="hljs-comment">// 按作者批量获取诗词</span><br>    String[] stringArray = &#123;<span class="hljs-string">&quot;李白&quot;</span>, <span class="hljs-string">&quot;杜甫&quot;</span>, <span class="hljs-string">&quot;苏轼&quot;</span>, <span class="hljs-string">&quot;王维&quot;</span>, <span class="hljs-string">&quot;杜牧&quot;</span>, <span class="hljs-string">&quot;陆游&quot;</span>, <span class="hljs-string">&quot;李煜&quot;</span>, <span class="hljs-string">&quot;元稹&quot;</span>, <span class="hljs-string">&quot;韩愈&quot;</span>, <span class="hljs-string">&quot;岑参&quot;</span>, <span class="hljs-string">&quot;齐己&quot;</span>&#125;;<br>    List&lt;String&gt; authorList = Arrays.asList(stringArray);<br><br>    <span class="hljs-type">String</span> <span class="hljs-variable">originUrl</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;https://so.gushiwen.cn/shiwens/default.aspx?page=%d&amp;tstr=&amp;astr=%s&amp;cstr=&amp;xstr=&quot;</span>;<br><br>    <span class="hljs-keyword">for</span> (String authorStr : authorList) &#123;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>; i &lt; <span class="hljs-number">5</span>; i++) &#123;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> String.format(originUrl, i, authorStr);<br>            <span class="hljs-comment">// 1. 获取数据</span><br>            <span class="hljs-type">Document</span> <span class="hljs-variable">doc</span> <span class="hljs-operator">=</span> Jsoup.connect(url)<br>                    .userAgent(<span class="hljs-string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.81&quot;</span>)<br>                    .get();<br><br>            <span class="hljs-comment">// 捕获 id = &quot;leftZhankai&quot;</span><br>            <span class="hljs-type">Element</span> <span class="hljs-variable">leftZhankai</span> <span class="hljs-operator">=</span> doc.getElementById(<span class="hljs-string">&quot;leftZhankai&quot;</span>);<br>            <span class="hljs-type">Elements</span> <span class="hljs-variable">heads</span> <span class="hljs-operator">=</span> leftZhankai.select(<span class="hljs-string">&quot;.sons .cont div:nth-of-type(2)&quot;</span>);<br><br>            ArrayList&lt;Post&gt; postList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>            <span class="hljs-keyword">for</span> (Element head : heads) &#123;<br>                <span class="hljs-type">Post</span> <span class="hljs-variable">post</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Post</span>();<br><br>                <span class="hljs-type">String</span> <span class="hljs-variable">title</span> <span class="hljs-operator">=</span> head.select(<span class="hljs-string">&quot;&gt;p:nth-of-type(1)&quot;</span>).text();<br>                <span class="hljs-type">String</span> <span class="hljs-variable">author</span> <span class="hljs-operator">=</span> head.select(<span class="hljs-string">&quot;&gt;p:nth-of-type(2)&quot;</span>).text();<br>                <span class="hljs-type">String</span> <span class="hljs-variable">content</span> <span class="hljs-operator">=</span> head.select(<span class="hljs-string">&quot;.contson&quot;</span>).text();<br>                System.out.println(title);<br>                System.out.println(author);<br>                System.out.println(content);<br><br>                System.out.println(<span class="hljs-string">&quot;------------------------------------&quot;</span>);<br><br>                post.setTitle(title);<br>                post.setAuthor(author);<br>                post.setContent(content);<br>                postList.add(post);<br>            &#125;<br><br>            <span class="hljs-type">boolean</span> <span class="hljs-variable">saveBatch</span> <span class="hljs-operator">=</span> postService.saveBatch(postList);<br>            ThrowUtils.throwIf(!saveBatch, ErrorCode.OPERATION_ERROR, <span class="hljs-string">&quot;批量插入诗词失败&quot;</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// 计时结束</span><br>    stopWatch.stop();<br>    <span class="hljs-comment">// 计算插入所用总时间</span><br>    System.out.println(stopWatch.getTotalTimeMillis() + <span class="hljs-string">&quot;ms&quot;</span>);<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213123016392.png" alt="image-20240213123016392"></p>
<p>1993 ms，2066ms，2847ms，2211ms，2459ms</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testFetchPoemByCompletable</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-comment">// new一个StopWatch对象</span><br>    <span class="hljs-type">StopWatch</span> <span class="hljs-variable">stopWatch</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StopWatch</span>();<br>    <span class="hljs-comment">// 计时开始</span><br>    stopWatch.start();<br><br>    <span class="hljs-comment">// 按作者批量获取诗词</span><br>    String[] stringArray = &#123;<span class="hljs-string">&quot;李白&quot;</span>, <span class="hljs-string">&quot;杜甫&quot;</span>, <span class="hljs-string">&quot;苏轼&quot;</span>, <span class="hljs-string">&quot;王维&quot;</span>, <span class="hljs-string">&quot;杜牧&quot;</span>, <span class="hljs-string">&quot;陆游&quot;</span>, <span class="hljs-string">&quot;李煜&quot;</span>, <span class="hljs-string">&quot;元稹&quot;</span>, <span class="hljs-string">&quot;韩愈&quot;</span>, <span class="hljs-string">&quot;岑参&quot;</span>, <span class="hljs-string">&quot;齐己&quot;</span>&#125;;<br>    List&lt;String&gt; authorList = Arrays.asList(stringArray);<br><br>    <span class="hljs-type">String</span> <span class="hljs-variable">originUrl</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;https://so.gushiwen.cn/shiwens/default.aspx?page=%d&amp;tstr=&amp;astr=%s&amp;cstr=&amp;xstr=&quot;</span>;<br><br>    <span class="hljs-comment">// 创建自定义线程池</span><br>    <span class="hljs-type">ExecutorService</span> <span class="hljs-variable">executorService</span> <span class="hljs-operator">=</span> Executors.newFixedThreadPool(<span class="hljs-number">5</span>);<br><br>    <span class="hljs-comment">// 存储所有的异步任务</span><br>    List&lt;CompletableFuture&lt;Void&gt;&gt; futures = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br><br>    <span class="hljs-keyword">for</span> (String authorStr : authorList) &#123;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>; i &lt; <span class="hljs-number">5</span>; i++) &#123;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> String.format(originUrl, i, authorStr);<br>            <span class="hljs-comment">// 创建异步任务</span><br>            CompletableFuture&lt;Void&gt; future = CompletableFuture.runAsync(() -&gt; &#123;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    <span class="hljs-comment">// 1. 获取数据</span><br>                    <span class="hljs-type">Document</span> <span class="hljs-variable">doc</span> <span class="hljs-operator">=</span> Jsoup.connect(url)<br>                            .userAgent(<span class="hljs-string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.81&quot;</span>)<br>                            .get();<br><br>                    <span class="hljs-comment">// 捕获 id = &quot;leftZhankai&quot;</span><br>                    <span class="hljs-type">Element</span> <span class="hljs-variable">leftZhankai</span> <span class="hljs-operator">=</span> doc.getElementById(<span class="hljs-string">&quot;leftZhankai&quot;</span>);<br>                    <span class="hljs-type">Elements</span> <span class="hljs-variable">heads</span> <span class="hljs-operator">=</span> leftZhankai.select(<span class="hljs-string">&quot;.sons .cont div:nth-of-type(2)&quot;</span>);<br><br>                    ArrayList&lt;Post&gt; postList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>                    <span class="hljs-keyword">for</span> (Element head : heads) &#123;<br>                        <span class="hljs-type">Post</span> <span class="hljs-variable">post</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Post</span>();<br><br>                        <span class="hljs-type">String</span> <span class="hljs-variable">title</span> <span class="hljs-operator">=</span> head.select(<span class="hljs-string">&quot;&gt;p:nth-of-type(1)&quot;</span>).text();<br>                        <span class="hljs-type">String</span> <span class="hljs-variable">author</span> <span class="hljs-operator">=</span> head.select(<span class="hljs-string">&quot;&gt;p:nth-of-type(2)&quot;</span>).text();<br>                        <span class="hljs-type">String</span> <span class="hljs-variable">content</span> <span class="hljs-operator">=</span> head.select(<span class="hljs-string">&quot;.contson&quot;</span>).text();<br>                        System.out.println(title);<br>                        System.out.println(author);<br>                        System.out.println(content);<br><br>                        System.out.println(<span class="hljs-string">&quot;------------------------------------&quot;</span>);<br><br>                        post.setTitle(title);<br>                        post.setAuthor(author);<br>                        post.setContent(content);<br>                        postList.add(post);<br>                    &#125;<br><br>                    <span class="hljs-type">boolean</span> <span class="hljs-variable">saveBatch</span> <span class="hljs-operator">=</span> postService.saveBatch(postList);<br>                    ThrowUtils.throwIf(!saveBatch, ErrorCode.OPERATION_ERROR, <span class="hljs-string">&quot;批量插入诗词失败&quot;</span>);<br>                &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                &#125;<br>            &#125;, executorService);<br><br>            <span class="hljs-comment">// 将异步任务添加到列表中</span><br>            futures.add(future);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// 等待所有异步任务完成</span><br>    CompletableFuture.allOf(futures.toArray(<span class="hljs-keyword">new</span> <span class="hljs-title class_">CompletableFuture</span>[<span class="hljs-number">0</span>])).join();<br>    <span class="hljs-comment">// 关闭线程池</span><br>    executorService.shutdown();<br>    <span class="hljs-comment">// 计时结束</span><br>    stopWatch.stop();<br>    <span class="hljs-comment">// 计算插入所用总时间</span><br>    System.out.println(stopWatch.getTotalTimeMillis() + <span class="hljs-string">&quot;ms&quot;</span>);<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213125219270.png" alt="image-20240213125219270"></p>
<p>1445ms，1265ms，1400ms，1199ms，1312ms</p>
<p>按作者批量获取诗词，二十四个作者，每位作者四页，每页10首，批量插入诗词记录到数据库（题目，作者，内容）</p>
<p>普通批量插入：4168ms ，异步编程批量插入：1719ms</p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213150539496.png" alt="image-20240213150539496"></p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213150326802.png" alt="image-20240213150326802"></p>
<h2 id="搜索词建议-1"><a href="#搜索词建议-1" class="headerlink" title="搜索词建议"></a>搜索词建议</h2><p>优化<strong>热门搜索统计</strong>：</p>
<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs tsx"><span class="hljs-comment">// 保存搜索词</span><br> <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title class_">SearchControllerService</span>.<span class="hljs-title function_">setHotWordsUsingGet</span>(searchText).<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> &#123;<br>   <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(res.<span class="hljs-property">data</span>)<br> &#125;)<br></code></pre></td></tr></table></figure>

<p>执行搜索，同步存放搜索词到 Elasticsearch 索引中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 同步保存搜索词到 Elasticsearch 中</span><br><span class="hljs-type">MessageEsDTO</span> <span class="hljs-variable">messageEsDTO</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEsDTO</span>();<br>BeanUtils.copyProperties(messageDTO, messageEsDTO);<br><br><span class="hljs-comment">// 生成唯一 id</span><br><span class="hljs-type">String</span> <span class="hljs-variable">uniqueId</span> <span class="hljs-operator">=</span> getUniqueId(searchText);<br>messageEsDTO.setId(uniqueId);<br><span class="hljs-type">MessageEsDTO</span> <span class="hljs-variable">save</span> <span class="hljs-operator">=</span> elasticsearchRestTemplate.save(messageEsDTO);<br>ThrowUtils.throwIf(ObjectUtils.isEmpty(save), ErrorCode.OPERATION_ERROR);<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 搜索词 ES 包装类</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> memory</span><br><span class="hljs-comment"> **/</span><br><span class="hljs-meta">@Document(indexName = &quot;search_text&quot;)</span><br><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MessageEsDTO</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 文章id</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Id</span><br>    <span class="hljs-keyword">private</span> String id;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 用户</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Long userId;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 搜索词条</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> String searchText;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>根据搜索词内容，使用 <strong>MD5 摘要算法</strong>生成唯一 id，同步保存数据到 Elasticsearch 中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 生成唯一 id</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> searchText 搜索词</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 唯一 id</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">private</span> String <span class="hljs-title function_">getUniqueId</span><span class="hljs-params">(String searchText)</span> &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-comment">// 获取MD5信息摘要实例</span><br>        <span class="hljs-type">MessageDigest</span> <span class="hljs-variable">md</span> <span class="hljs-operator">=</span> MessageDigest.getInstance(<span class="hljs-string">&quot;MD5&quot;</span>);<br>        <span class="hljs-comment">// 计算搜索词的MD5哈希值</span><br>        <span class="hljs-type">byte</span>[] hashBytes = md.digest(searchText.getBytes());<br><br>        <span class="hljs-type">StringBuilder</span> <span class="hljs-variable">hexString</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringBuilder</span>();<br>        <span class="hljs-comment">// 将字节数组转换为十六进制字符串</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">byte</span> b : hashBytes) &#123;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">hex</span> <span class="hljs-operator">=</span> Integer.toHexString(<span class="hljs-number">0xff</span> &amp; b);<br>            <span class="hljs-keyword">if</span> (hex.length() == <span class="hljs-number">1</span>) hexString.append(<span class="hljs-string">&#x27;0&#x27;</span>);<br>            hexString.append(hex);<br>        &#125;<br><br>        <span class="hljs-comment">// 生成最终的唯一ID，使用uniqueId作为文档的ID</span><br>        <span class="hljs-keyword">return</span> hexString.toString();<br>    &#125; <span class="hljs-keyword">catch</span> (NoSuchAlgorithmException e) &#123;<br>        <span class="hljs-comment">// 处理异常</span><br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>效果如下，Elasticsearch 索引中仅保存搜索词条，无需关注词条被搜索次数</p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213181525090.png" alt="image-20240213181525090"></p>
<p>新增搜索词索引，searchText 字段添加 suggest 属性，支持<strong>前缀搜索建议</strong>：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs bash">PUT /search_text<br>&#123;<br>    <span class="hljs-string">&quot;mappings&quot;</span> : &#123;<br>      <span class="hljs-string">&quot;properties&quot;</span> : &#123;<br>          <span class="hljs-string">&quot;id&quot;</span> : &#123;<br>          <span class="hljs-string">&quot;type&quot;</span> : <span class="hljs-string">&quot;text&quot;</span>,<br>          <span class="hljs-string">&quot;fields&quot;</span> : &#123;<br>            <span class="hljs-string">&quot;keyword&quot;</span> : &#123;<br>              <span class="hljs-string">&quot;type&quot;</span> : <span class="hljs-string">&quot;keyword&quot;</span>,<br>              <span class="hljs-string">&quot;ignore_above&quot;</span> : 256<br>            &#125;<br>          &#125;<br>        &#125;,<br>        <span class="hljs-string">&quot;searchText&quot;</span> : &#123;<br>           <span class="hljs-string">&quot;type&quot;</span> : <span class="hljs-string">&quot;text&quot;</span>,<br>          <span class="hljs-string">&quot;fields&quot;</span> : &#123;<br>            <span class="hljs-string">&quot;keyword&quot;</span> : &#123;<br>              <span class="hljs-string">&quot;type&quot;</span> : <span class="hljs-string">&quot;keyword&quot;</span>,<br>              <span class="hljs-string">&quot;ignore_above&quot;</span> : 256<br>            &#125;,<br>            <span class="hljs-string">&quot;suggest&quot;</span> : &#123;<br>              <span class="hljs-string">&quot;type&quot;</span> : <span class="hljs-string">&quot;completion&quot;</span>,<br>              <span class="hljs-string">&quot;analyzer&quot;</span> : <span class="hljs-string">&quot;ik_max_word&quot;</span>,<br>              <span class="hljs-string">&quot;preserve_separators&quot;</span> : <span class="hljs-literal">true</span>,<br>              <span class="hljs-string">&quot;preserve_position_increments&quot;</span> : <span class="hljs-literal">true</span>,<br>              <span class="hljs-string">&quot;max_input_length&quot;</span> : 50<br>            &#125;<br>          &#125;,<br>          <span class="hljs-string">&quot;analyzer&quot;</span> : <span class="hljs-string">&quot;ik_max_word&quot;</span>,<br>          <span class="hljs-string">&quot;search_analyzer&quot;</span> : <span class="hljs-string">&quot;ik_smart&quot;</span><br>        &#125;,<br>        <span class="hljs-string">&quot;userId&quot;</span> : &#123;<br>          <span class="hljs-string">&quot;type&quot;</span> : <span class="hljs-string">&quot;long&quot;</span><br>        &#125;<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>执行搜索，保存搜索词条到 Elasticsearch 索引中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 同步保存搜索词到 Elasticsearch 中</span><br><span class="hljs-type">MessageEsDTO</span> <span class="hljs-variable">messageEsDTO</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEsDTO</span>();<br>BeanUtils.copyProperties(messageDTO, messageEsDTO);<br><br><span class="hljs-comment">// 生成唯一 id</span><br><span class="hljs-type">String</span> <span class="hljs-variable">uniqueId</span> <span class="hljs-operator">=</span> getUniqueId(searchText);<br>messageEsDTO.setId(uniqueId);<br><span class="hljs-type">MessageEsDTO</span> <span class="hljs-variable">save</span> <span class="hljs-operator">=</span> elasticsearchRestTemplate.save(messageEsDTO);<br>ThrowUtils.throwIf(ObjectUtils.isEmpty(save), ErrorCode.OPERATION_ERROR);<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213191453168.png" alt="image-20240213191453168"></p>
<p>根据搜索词条，从历史搜索词条中返回搜索建议（可优化，从热门词条中返回搜索建议）：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 搜索建议</span><br><span class="hljs-type">SuggestBuilder</span> <span class="hljs-variable">suggestBuilder</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">SuggestBuilder</span>()<br>        .addSuggestion(<span class="hljs-string">&quot;suggestionTitle&quot;</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">CompletionSuggestionBuilder</span>(<span class="hljs-string">&quot;searchText.suggest&quot;</span>).skipDuplicates(<span class="hljs-literal">true</span>).size(<span class="hljs-number">5</span>).prefix(suggestText));<br><br><span class="hljs-comment">// 构造查询</span><br><span class="hljs-type">NativeSearchQuery</span> <span class="hljs-variable">searchQuery</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NativeSearchQueryBuilder</span>()<br>        .withSuggestBuilder(suggestBuilder)<br>        .build();<br><br><span class="hljs-comment">// 执行搜索</span><br>SearchHits&lt;MessageEsDTO&gt; searchHits = elasticsearchRestTemplate.search(searchQuery, MessageEsDTO.class);<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 搜索建议词列表</span><br>ArrayList&lt;String&gt; suggestionList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br><br><span class="hljs-comment">// 从 SearchResponse 中获取建议</span><br><span class="hljs-type">Suggest</span> <span class="hljs-variable">suggest</span> <span class="hljs-operator">=</span> searchHits.getSuggest();<br><span class="hljs-keyword">if</span> (suggest != <span class="hljs-literal">null</span>) &#123;<br>    <span class="hljs-comment">// 获取特定的建议结果</span><br>    Suggest.Suggestion&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Suggest</span>.Suggestion.Entry&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Suggest</span>.Suggestion.Entry.Option&gt;&gt; termSuggestion = suggest.getSuggestion(<span class="hljs-string">&quot;suggestionTitle&quot;</span>);<br>    <span class="hljs-comment">// 遍历建议选项</span><br>    <span class="hljs-keyword">for</span> (Suggest.Suggestion.Entry&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Suggest</span>.Suggestion.Entry.Option&gt; entry : termSuggestion.getEntries()) &#123;<br>        <span class="hljs-comment">// 处理每个建议选项，例如打印它们</span><br>        <span class="hljs-keyword">for</span> (Suggest.Suggestion.Entry.Option option : entry.getOptions()) &#123;<br>            <span class="hljs-keyword">if</span> (option != <span class="hljs-literal">null</span>) &#123;<br>                <span class="hljs-type">String</span> <span class="hljs-variable">text</span> <span class="hljs-operator">=</span> option.getText();<br>                suggestionList.add(text);<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213194835349.png" alt="image-20240213194835349"></p>
<h2 id="文章爬取"><a href="#文章爬取" class="headerlink" title="文章爬取"></a>文章爬取</h2><p>妈的，标题还有带表情的：</p>
<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20240213232409333.png"></p>
<h2 id="亮点"><a href="#亮点" class="headerlink" title="亮点"></a>亮点</h2><p>设计模式：适配器模式，注册器模式，门面模式</p>
<p>数据抓取：</p>
<ul>
<li>爬虫获取外部资源，爬取图片、视频，<strong>异步处理</strong>耗时请求</li>
<li>定时任务：<strong>定时</strong>同步（爬取）外部文章资源</li>
<li>诗词抓取：异步编程，按作者批量获取诗词，二十四个作者，每位作者四页，每页10首，批量插入诗词记录到数据库（题目，作者，内容）</li>
<li>文章爬取：</li>
<li>图片爬取：</li>
</ul>
<p>数据同步方式：</p>
<ul>
<li>自主编码实现 MySQL -&gt; ES 数据同步；现成中间件：</li>
<li>Logstash 数据同步管道，配置输入输出源；</li>
<li>Canal + MQ 实现监听数据库流水（设想）</li>
</ul>
<p>Elastic Stack 技术栈：</p>
<ul>
<li>ES 和 MySQL 的比较，ES 的概念，要解决的问题</li>
<li>DSL 语法：简单索引的增删改，文档的增删改，复合查询、全文搜索、term 查询、聚合查询、布尔查询，搜索建议词、关键词</li>
<li>分词器：内置分词器、IK 分词器</li>
<li>Java 操作 ES：ES 官方提供 API、Spring Data Elasticsearch</li>
<li>Logstash 数据同步管道：配置输入输出源，同步 MySQL -&gt; ES 的数据</li>
<li>Kibana 数据看板：搭建数据看板，数据分析</li>
<li>ES 实现搜索词条建议、关键词语高亮、热门搜索统计、热门话题分析</li>
</ul>
<p>Redis：</p>
<ul>
<li><p>Redis 基本的数据结构类型</p>
</li>
<li><p>使用 Zset 实现热门搜索词（排行榜）</p>
</li>
<li><p>保留近期内的搜索数据（图片，视频），保存 24 小时</p>
</li>
</ul>
<p>用户：</p>
<ul>
<li>sessionId / ThreadLocal 保存登录用户信息</li>
</ul>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">用户会话管理：在Web应用中，为了跟踪用户的会话信息，通常会将用户会话数据存储在HttpSession对象中。然而，由于每个请求可能由不同的线程处理，直接使用HttpSession会导致线程安全问题。使用ThreadLocal可以将用户会话数据存储在每个线程中，确保每个线程都能独立地访问用户会话数据。<br></code></pre></td></tr></table></figure>

<ul>
<li>使用 Markdown 编辑器发布文章，<strong>交由审核</strong></li>
<li>单设备登录限制</li>
</ul>
<p>权限校验：</p>
<ul>
<li>前端，校验是否登录；根据用户身份，限制普通用户访问页面</li>
<li>后端：全局过滤器，限制页面访问权限，校验登录</li>
<li>Spring AOP + 自定义注解，实现全局请求响应拦截和权限校验</li>
</ul>
<p>高流量：如何抵御大量搜索请求，保证数据记录正确</p>
<ul>
<li>缓存：保留近期内的搜索数据（图片，视频），保存 24 小时</li>
<li>限流：四大限流算法，简单限流、滑动窗口限流、漏桶限流、令牌桶限流</li>
<li>降级：</li>
</ul>
<p>并发编程：对于耗时请求，异步处理：从外源搜索图片、视频</p>
<p>Spring：</p>
<ul>
<li>Spring AOP + 自定义注解，实现全局请求响应拦截和权限校验</li>
<li>多环境配置：配置 开发环境、生产环境，方便项目开发、测试、部署上线</li>
</ul>
<p>其他：</p>
<p>接口文档、Markdown 文档编辑器</p>
<h2 id="思考："><a href="#思考：" class="headerlink" title="思考："></a>思考：</h2><h3 id="如何分析文章热度？"><a href="#如何分析文章热度？" class="headerlink" title="如何分析文章热度？"></a>如何分析文章热度？</h3><h3 id="Elasticseach-数据存储在硬盘内，如何选择合适的删除策略，清理过期数据呢"><a href="#Elasticseach-数据存储在硬盘内，如何选择合适的删除策略，清理过期数据呢" class="headerlink" title="Elasticseach 数据存储在硬盘内，如何选择合适的删除策略，清理过期数据呢"></a>Elasticseach 数据存储在硬盘内，如何选择合适的删除策略，清理过期数据呢</h3><p>Elasticsearch一个索引内可以存放的数据条数并没有固定的上限，它主要取决于以下几个因素：</p>
<ol>
<li><strong>磁盘空间</strong>：索引的大小受限于可用磁盘空间。Elasticsearch会将索引数据存储在磁盘上，因此磁盘空间是限制索引大小的主要因素。</li>
<li><strong>分片配置</strong>：Elasticsearch通过分片（shards）来水平扩展数据，每个索引可以包含一个或多个分片。默认情况下，一个索引会有5个主分片（primary shards），但可以在创建索引时指定不同的分片数。分片数越多，索引可以容纳的数据量就越大，但同时也会增加集群的管理复杂性。</li>
<li><strong>文档大小</strong>：单个文档的大小也会影响索引的容量。虽然Elasticsearch支持非常大的文档，但过大的文档可能会降低写入性能并增加索引的存储需求。</li>
</ol>
<p>当索引达到其存储限制时，Elasticsearch不会直接拒绝新的数据写入。相反，它会根据配置的策略来处理这种情况。常见的删除策略包括：</p>
<ol>
<li><strong>基于时间的删除</strong>：使用索引生命周期管理（ILM）策略，根据时间戳字段自动删除旧数据。例如，可以设置一个策略，使得索引在创建后的一定时间后被删除。</li>
<li><strong>基于大小的删除</strong>：当索引达到一定的大小时，可以配置ILM策略来删除旧数据，或者通过手动干预来删除不需要的数据。</li>
<li><strong>基于文档数量的删除</strong>：虽然Elasticsearch不直接基于文档数量限制索引大小，但可以通过删除旧文档来管理索引的大小。这可以通过编写删除查询或使用ILM策略来实现。</li>
<li><strong>滚动索引</strong>：在某些场景中，可以使用滚动索引（rolling indices）模式，即定期创建新的索引来存储新数据，而旧索引可以被删除或归档。这种策略常见于日志收集和分析场景。</li>
</ol>
<p>在实际应用中，建议根据业务需求和数据特点来选择合适的删除策略。同时，监控Elasticsearch集群的健康状况和性能，确保数据的有效管理和系统的稳定运行。</p>
<p>Elasticsearch 默认不提供自动删除旧数据的策略。默认情况下，索引会一直存在，直到你手动删除它们或者磁盘空间耗尽。但是，Elasticsearch 提供了索引生命周期管理（ILM）功能，允许你定义自己的删除策略。</p>
<p>当你创建一个索引时，除非你明确指定了ILM策略，否则该索引不会受到任何自动删除策略的影响。这意味着你需要主动管理索引的生命周期，包括决定何时删除不再需要的索引。</p>
<p>在ILM中，你可以定义策略来根据索引的年龄、大小或其他条件自动将索引移动到只读状态，并最终删除它们。但是，如果你不配置ILM或使用默认的ILM策略，索引将不会自动被删除。</p>
<p>因此，为了有效地管理Elasticsearch中的索引和数据，建议配置适当的ILM策略，以确保旧数据在不再需要时被自动删除，从而释放磁盘空间并优化集群性能。</p>
<h3 id="如何确保-Elasticsearch-文档的-id-唯一性？"><a href="#如何确保-Elasticsearch-文档的-id-唯一性？" class="headerlink" title="如何确保 Elasticsearch 文档的 id 唯一性？"></a>如何确保 Elasticsearch 文档的 id 唯一性？</h3><p>在Elasticsearch中记录搜索词，每条搜索词作为一个文档，保证数据ID的唯一性是非常重要的。Elasticsearch使用文档ID来唯一标识每个文档，因此你需要确保每个搜索词都被分配一个唯一的ID。以下是一些方法来实现这一点：</p>
<ol>
<li><p><strong>使用UUID</strong>：<br>UUID（Universally Unique Identifier）是一种广泛使用的唯一标识符生成方法。你可以为每个搜索词生成一个UUID，并将其用作文档的ID。UUID具有全局唯一性，可以确保即使在不同的系统或时间点上生成的ID也不会冲突。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.UUID;  <br> <br><span class="hljs-type">String</span> <span class="hljs-variable">uniqueId</span> <span class="hljs-operator">=</span> UUID.randomUUID().toString();  <br><span class="hljs-comment">// 使用uniqueId作为文档的ID</span><br></code></pre></td></tr></table></figure></li>
<li><p><strong>基于时间戳和搜索词生成ID</strong>：<br>如果你的搜索词量不是特别大，并且你希望ID具有一定的可读性或者顺序性，你可以考虑使用时间戳和搜索词来生成ID。例如，你可以将时间戳和搜索词拼接起来，并可能加上一个前缀或后缀来确保唯一性。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.time.Instant;  <br> <br><span class="hljs-type">String</span> <span class="hljs-variable">timestamp</span> <span class="hljs-operator">=</span> Instant.now().toString().replace(<span class="hljs-string">&quot;-&quot;</span>, <span class="hljs-string">&quot;&quot;</span>); <span class="hljs-comment">// 移除时间戳中的&quot;-&quot;字符  </span><br><span class="hljs-type">String</span> <span class="hljs-variable">searchTerm</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;你的搜索词&quot;</span>;  <br><span class="hljs-type">String</span> <span class="hljs-variable">uniqueId</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;search-&quot;</span> + timestamp + <span class="hljs-string">&quot;-&quot;</span> + searchTerm;  <br><span class="hljs-comment">// 使用uniqueId作为文档的ID</span><br></code></pre></td></tr></table></figure></li>
<li><p><strong>使用自增ID</strong>：<br>如果你的搜索词量不是很大，并且你不需要跨多个节点或集群保证唯一性，你可以考虑使用自增的ID。但是，请注意，如果你有多个节点或者需要扩展集群，自增ID可能会导致ID冲突。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">AtomicInteger</span> <span class="hljs-variable">counter</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AtomicInteger</span>(<span class="hljs-number">0</span>);  <br><span class="hljs-type">String</span> <span class="hljs-variable">uniqueId</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;search-&quot;</span> + counter.getAndIncrement();  <br><span class="hljs-comment">// 使用uniqueId作为文档的ID</span><br></code></pre></td></tr></table></figure></li>
<li><p><strong>使用哈希函数</strong>：<br>如果你希望ID更短，你可以考虑使用哈希函数（如MD5或SHA-1）对搜索词进行哈希处理，并将结果作为文档的ID。但请注意，哈希函数可能会产生相同的输出（即哈希碰撞），尽管这种可能性非常低。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.security.MessageDigest;  <br><span class="hljs-keyword">import</span> java.security.NoSuchAlgorithmException;  <br> <br><span class="hljs-type">String</span> <span class="hljs-variable">searchTerm</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;你的搜索词&quot;</span>;  <br><span class="hljs-keyword">try</span> &#123;  <br>    <span class="hljs-type">MessageDigest</span> <span class="hljs-variable">md</span> <span class="hljs-operator">=</span> MessageDigest.getInstance(<span class="hljs-string">&quot;MD5&quot;</span>);  <br>    <span class="hljs-type">byte</span>[] hashBytes = md.digest(searchTerm.getBytes());  <br>    <span class="hljs-type">StringBuilder</span> <span class="hljs-variable">hexString</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringBuilder</span>();  <br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">byte</span> b : hashBytes) &#123;  <br>        <span class="hljs-type">String</span> <span class="hljs-variable">hex</span> <span class="hljs-operator">=</span> Integer.toHexString(<span class="hljs-number">0xff</span> &amp; b);  <br>        <span class="hljs-keyword">if</span> (hex.length() == <span class="hljs-number">1</span>) hexString.append(<span class="hljs-string">&#x27;0&#x27;</span>);  <br>        hexString.append(hex);  <br>    &#125;  <br>    <span class="hljs-type">String</span> <span class="hljs-variable">uniqueId</span> <span class="hljs-operator">=</span> hexString.toString();  <br>    <span class="hljs-comment">// 使用uniqueId作为文档的ID  </span><br>&#125; <span class="hljs-keyword">catch</span> (NoSuchAlgorithmException e) &#123;  <br>    <span class="hljs-comment">// 处理异常  </span><br>&#125;<br></code></pre></td></tr></table></figure></li>
</ol>
<p>无论你选择哪种方法，都需要确保生成的ID在整个Elasticsearch集群中是唯一的。如果你使用了多节点集群，并且需要在不同节点之间保证ID的唯一性，那么使用UUID或基于时间戳和搜索词的方法通常是更好的选择。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><h2 id="踩坑记录"><a href="#踩坑记录" class="headerlink" title="踩坑记录"></a>踩坑记录</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> Page&lt;User&gt; <span class="hljs-title function_">listUserVOByPage</span><span class="hljs-params">(UserQueryRequest userQueryRequest, HttpServletRequest request)</span> &#123;<br>    <span class="hljs-type">long</span> <span class="hljs-variable">pageSize</span> <span class="hljs-operator">=</span> userQueryRequest.getPageSize();<br>    <span class="hljs-type">long</span> <span class="hljs-variable">current</span> <span class="hljs-operator">=</span> userQueryRequest.getCurrent();<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.page(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Page</span>&lt;&gt;(pageSize, current),<br>            <span class="hljs-built_in">this</span>.getQueryWrapper(userQueryRequest));<br>&#125;<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="Page对象的pageSize和currentPage写反了，拿取第1页、共10条数据，结果变成了第10页，共1条。（2023-08-31午）"><a href="#Page对象的pageSize和currentPage写反了，拿取第1页、共10条数据，结果变成了第10页，共1条。（2023-08-31午）" class="headerlink" title="Page对象的pageSize和currentPage写反了，拿取第1页、共10条数据，结果变成了第10页，共1条。（2023/08/31午）"></a>Page对象的pageSize和currentPage写反了，拿取第1页、共10条数据，结果变成了第10页，共1条。（2023/08/31午）</h5></li>
</ul>
<h3 id="搜索词条不兼容中文"><a href="#搜索词条不兼容中文" class="headerlink" title="搜索词条不兼容中文"></a>搜索词条不兼容中文</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">if</span> (StringUtils.isNotBlank(searchText)) &#123;<br>    searchText = URLEncoder.encode(searchText, <span class="hljs-string">&quot;UTF-8&quot;</span>);<br>&#125;<br><span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> String.format(<span class="hljs-string">&quot;https://cn.bing.com/images/search?q=%s&amp;first=%s&quot;</span>, searchText, current);<br></code></pre></td></tr></table></figure>

<ul>
<li><h5 id="转码就可以解决，如上👆"><a href="#转码就可以解决，如上👆" class="headerlink" title="转码就可以解决，如上👆"></a>转码就可以解决，如上👆</h5></li>
</ul>
<h3 id="适配器模式-1"><a href="#适配器模式-1" class="headerlink" title="适配器模式"></a>适配器模式</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Resource</span><br>  <span class="hljs-keyword">private</span> UserDataSource userDataSource;<br>  <span class="hljs-meta">@Resource</span><br>  <span class="hljs-keyword">private</span> PostDataSource postDataSource;<br>  <span class="hljs-meta">@Resource</span><br>  <span class="hljs-keyword">private</span> PictureDataSource pictureDataSource;<br><br>  <span class="hljs-meta">@Override</span><br>  <span class="hljs-keyword">public</span> SearchVO <span class="hljs-title function_">searchAll</span><span class="hljs-params">(SearchQueryRequest searchQueryRequest, HttpServletRequest request)</span> &#123;<br>      <span class="hljs-comment">// 1.校验参数type</span><br>      <span class="hljs-comment">// 1.1.检查type类型</span><br>      <span class="hljs-type">String</span> <span class="hljs-variable">type</span> <span class="hljs-operator">=</span> searchQueryRequest.getType();<br>      <span class="hljs-comment">// 1.2.校验参数正确性</span><br>      ThrowUtils.throwIf(StringUtils.isEmpty(type), ErrorCode.PARAMS_ERROR);<br>      <span class="hljs-comment">// 1.3.获取页面类型</span><br>      <span class="hljs-type">SearchTypeEnum</span> <span class="hljs-variable">enumByValue</span> <span class="hljs-operator">=</span> SearchTypeEnum.getEnumByValue(type);<br><br>      <span class="hljs-type">String</span> <span class="hljs-variable">searchText</span> <span class="hljs-operator">=</span> searchQueryRequest.getSearchText();<br>      <span class="hljs-type">long</span> <span class="hljs-variable">pageSize</span> <span class="hljs-operator">=</span> searchQueryRequest.getPageSize();<br>      <span class="hljs-type">long</span> <span class="hljs-variable">current</span> <span class="hljs-operator">=</span> searchQueryRequest.getCurrent();<br><br>      <span class="hljs-comment">// 2.执行查询全部数据</span><br>      <span class="hljs-type">SearchVO</span> <span class="hljs-variable">searchVO</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br><br>      <span class="hljs-keyword">if</span> (enumByValue == <span class="hljs-literal">null</span>) &#123;<br>          <span class="hljs-comment">// 获取文章</span><br>          CompletableFuture&lt;Page&lt;PostVO&gt;&gt; postTask = CompletableFuture.supplyAsync(() -&gt; &#123;<br>              <span class="hljs-keyword">return</span> postDataSource.search(searchText, pageSize, current);<br>          &#125;);<br><br>          <span class="hljs-comment">// 获取用户</span><br>          CompletableFuture&lt;Page&lt;User&gt;&gt; userTask = CompletableFuture.supplyAsync(() -&gt; &#123;<br>              <span class="hljs-keyword">return</span> userDataSource.search(searchText, pageSize, current);<br>          &#125;);<br><br>          <span class="hljs-comment">// 获取图片</span><br>          CompletableFuture&lt;Page&lt;Picture&gt;&gt; pictureTask = CompletableFuture.supplyAsync(() -&gt; &#123;<br>              <span class="hljs-keyword">try</span> &#123;<br>                  <span class="hljs-keyword">return</span> pictureDataSource.search(searchText,pageSize,current);<br>              &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>                  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>              &#125;<br>          &#125;);<br><br>          CompletableFuture.allOf(userTask, postTask, pictureTask).join();<br><br>          <span class="hljs-keyword">try</span> &#123;<br>              Page&lt;PostVO&gt; postVOPage = postTask.get();<br>              Page&lt;User&gt; userPage = userTask.get();<br>              Page&lt;Picture&gt; picturePage = pictureTask.get();<br><br>              searchVO = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchVO</span>();<br>              searchVO.setUserList(userPage.getRecords());<br>              searchVO.setPostList(postVOPage.getRecords());<br>              searchVO.setPictureList(picturePage.getRecords());<br>          &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>              <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>          &#125; <span class="hljs-keyword">catch</span> (ExecutionException e) &#123;<br>              <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>          &#125;<br>      &#125; <br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">else</span> &#123;<br>           <span class="hljs-comment">// 分类查询</span><br>           searchVO = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchVO</span>();<br>           <span class="hljs-keyword">switch</span> (enumByValue) &#123;<br>               <span class="hljs-keyword">case</span> POST:<br>                   <span class="hljs-type">PostQueryRequest</span> <span class="hljs-variable">postQueryRequest</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">PostQueryRequest</span>();<br>                   postQueryRequest.setSearchText(searchText);<br>                   postQueryRequest.setPageSize(pageSize);<br>                   postQueryRequest.setCurrent(current);<br>                   Page&lt;PostVO&gt; postVOPage = postService.listPostVOByPage(postQueryRequest, request);<br><br>                   searchVO.setPostList(postVOPage.getRecords());<br>               <span class="hljs-keyword">case</span> USER:<br>                   <span class="hljs-type">UserQueryRequest</span> <span class="hljs-variable">userQueryRequest</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserQueryRequest</span>();<br>                   userQueryRequest.setUserName(searchText);<br>                   userQueryRequest.setPageSize(pageSize);<br>                   userQueryRequest.setCurrent(current);<br>                   Page&lt;User&gt; userPage = userService.listUserVOByPage(userQueryRequest, request);<br><br>                   searchVO.setUserList(userPage.getRecords());<br>               <span class="hljs-keyword">case</span> PICTURE:<br>                   Page&lt;Picture&gt; picturePage = <span class="hljs-literal">null</span>;<br>                   <span class="hljs-keyword">try</span> &#123;<br>                       picturePage = pictureService.listPictureVOByPage(searchText, pageSize, current);<br>                   &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>                       <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>                   &#125; <span class="hljs-keyword">finally</span> &#123;<br>                       searchVO.setPictureList(picturePage.getRecords());<br>                   &#125;<br>           &#125;<br>       &#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-comment">// 分类查询</span><br>            searchVO = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchVO</span>();<br>            DataSource&lt;?&gt; dataSourceByType = dataSourceRegistry.getDataSourceByType(type);<br>            <span class="hljs-keyword">try</span> &#123;<br>                Page&lt;?&gt; page = dataSourceByType.search(searchText, pageSize, current);<br>                searchVO.setDataList(page.getRecords());<br>            &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> searchVO;<br></code></pre></td></tr></table></figure>

<h3 id="索引关闭"><a href="#索引关闭" class="headerlink" title="索引关闭"></a>索引关闭</h3><p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203203034571.png" alt="image-20231203203034571"></p>
<ul>
<li>本来好好的，突然就这样了，这索引记录也没删呢<strong>（2023/12/03晚）</strong></li>
</ul>
<blockquote>
<p>Elasticsearch 中的索引可以被关闭，这是为了优化性能和资源使用。一旦索引被关闭，就不能再对其进行写入操作，但仍然可以进行读取操作。如果你试图对一个已关闭的索引进行写入操作，就会遇到 “index_closed_exception” 错误。</p>
<p>解决方案：重新打开你的索引。你可以使用以下命令重新打开一个索引：</p>
</blockquote>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">POST <span class="hljs-regexp">/post_v1/</span>_open<br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/deng-2022/pictures/raw/master/images/image-20231203203222431.png" alt="image-20231203203222431"></p>
<ul>
<li>这样就正常查询了，同样的，也可以关闭索引：<strong>（2023/12/03晚）</strong></li>
</ul>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">POST <span class="hljs-regexp">/post_v1/</span>_close<br></code></pre></td></tr></table></figure>



<h2 id="TODO"><a href="#TODO" class="headerlink" title="TODO"></a>TODO</h2>
                
              </div>
            
            <hr/>
            <div>
              <div class="post-metas my-3">
  
    <div class="post-meta mr-3 d-flex align-items-center">
      <i class="iconfont icon-category"></i>
      

<span class="category-chains">
  
  
    
      <span class="category-chain">
        
  <a href="/memory_blog/categories/%E9%A1%B9%E7%9B%AE/" class="category-chain-item">项目</a>
  
  
    <span>></span>
    
  <a href="/memory_blog/categories/%E9%A1%B9%E7%9B%AE/%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C/" class="category-chain-item">开发经验</a>
  
  
    <span>></span>
    
  <a href="/memory_blog/categories/%E9%A1%B9%E7%9B%AE/%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C/Elasticsearch/" class="category-chain-item">Elasticsearch</a>
  
  
    <span>></span>
    
  <a href="/memory_blog/categories/%E9%A1%B9%E7%9B%AE/%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C/Elasticsearch/Logstash/" class="category-chain-item">Logstash</a>
  
  
    <span>></span>
    
  <a href="/memory_blog/categories/%E9%A1%B9%E7%9B%AE/%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C/Elasticsearch/Logstash/Kibana/" class="category-chain-item">Kibana</a>
  
  

  

  

  

  

      </span>
    
  
</span>

    </div>
  
  
    <div class="post-meta">
      <i class="iconfont icon-tags"></i>
      
        <a href="/memory_blog/tags/%E9%A1%B9%E7%9B%AE/">#项目</a>
      
        <a href="/memory_blog/tags/%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C/">#开发经验</a>
      
        <a href="/memory_blog/tags/Elasticsearch/">#Elasticsearch</a>
      
        <a href="/memory_blog/tags/Logstash/">#Logstash</a>
      
        <a href="/memory_blog/tags/Kibana/">#Kibana</a>
      
    </div>
  
</div>


              
  

  <div class="license-box my-3">
    <div class="license-title">
      <div>MemorySearch 忆搜阁-开发文档</div>
      <div>https://test.atomgit.net/memory_blog/2023/08/26/MemorySearch 忆搜阁-开发文档/</div>
    </div>
    <div class="license-meta">
      
        <div class="license-meta-item">
          <div>作者</div>
          <div>Memory</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>发布于</div>
          <div>2023年8月26日</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>更新于</div>
          <div>2024年2月19日</div>
        </div>
      
      
        <div class="license-meta-item">
          <div>许可协议</div>
          <div>
            
              
              
                <a target="_blank" href="https://creativecommons.org/licenses/by/4.0/">
                  <span class="hint--top hint--rounded" aria-label="BY - 署名">
                    <i class="iconfont icon-by"></i>
                  </span>
                </a>
              
            
          </div>
        </div>
      
    </div>
    <div class="license-icon iconfont"></div>
  </div>



              
                <div class="post-prevnext my-3">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/memory_blog/2023/09/10/Memory%20%E7%BC%98%E5%BF%86%E4%BA%A4%E5%8F%8B%E7%A4%BE%E5%8C%BA-%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/" title="Memory 缘忆交友社区-开发文档">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">Memory 缘忆交友社区-开发文档</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/memory_blog/2023/08/24/Maven%E5%A5%87%E6%8A%80%E6%B7%AB%E5%B7%A7%EF%BC%9A%E4%BC%98%E5%8C%96%E9%A1%B9%E7%9B%AE%E6%9E%84%E5%BB%BA%E4%B8%8E%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98/" title="Maven奇技淫巧：优化项目构建与性能调优">
                        <span class="hidden-mobile">Maven奇技淫巧：优化项目构建与性能调优</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
  
  
    <article id="comments">
      

    </article>
  


          </article>
        </div>
      </div>
    </div>

    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar" style="margin-left: -1rem">
    <div id="toc">
  <p class="toc-header">
    <i class="iconfont icon-list"></i>
    <span>目录</span>
  </p>
  <div class="toc-body" id="toc-body"></div>
</div>



  </aside>


    </div>
  </div>
</div>





  



  



  



  



  







    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v" for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>

    

    
  </main>

  <footer>
    <div class="footer-inner">
  
    <div class="footer-content">
       <a href="https://hexo.fluid-dev.com/docs/guide/" target="_blank" rel="nofollow noopener"> <span>Memory | 个人文档站点</span> </a>
<i class="iconfont icon-love"></i> <a href="https://hexo.fluid-dev.com/docs/guide/" target="_blank" rel="nofollow noopener"> <span>配置指南 | Hexo Fluid 用户手册</span> </a>   
    </div>
  
  
    <div class="statistics">
  
  

  
    
      <span id="leancloud-site-pv-container" style="display: none">
        总访问量 
        <span id="leancloud-site-pv"></span>
         次
      </span>
    
    
      <span id="leancloud-site-uv-container" style="display: none">
        总访客数 
        <span id="leancloud-site-uv"></span>
         人
      </span>
    
    

  
</div>

  
  
    <!-- 备案信息 ICP for China -->
    <div class="beian">
  <span>
    <a href="http://beian.miit.gov.cn/" target="_blank" rel="nofollow noopener">
      京ICP证123456号
    </a>
  </span>
  
    
      <span>
        <a
          href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=12345678"
          rel="nofollow noopener"
          class="beian-police"
          target="_blank"
        >
          
            <span style="visibility: hidden; width: 0">|</span>
            <img src="/memory_blog/img/police_beian.png" alt="police-icon"/>
          
          <span>京公网安备12345678号</span>
        </a>
      </span>
    
  
</div>

  
  
</div>

  </footer>

  <!-- Scripts -->
  
  <script  src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js" ></script>
<script  src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js" ></script>
<script  src="/memory_blog/js/events.js" ></script>
<script  src="/memory_blog/js/plugins.js" ></script>


  <script  src="https://lib.baomitu.com/typed.js/2.0.12/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var subtitle = document.getElementById('subtitle');
      if (!subtitle || !typing) {
        return;
      }
      var text = subtitle.getAttribute('data-typed-text');
      
        typing(text);
      
    })(window, document);
  </script>







  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/tocbot/4.18.2/tocbot.min.js', function() {
    var toc = jQuery('#toc');
    if (toc.length === 0 || !window.tocbot) { return; }
    var boardCtn = jQuery('#board-ctn');
    var boardTop = boardCtn.offset().top;

    window.tocbot.init(Object.assign({
      tocSelector     : '#toc-body',
      contentSelector : '.markdown-body',
      linkClass       : 'tocbot-link',
      activeLinkClass : 'tocbot-active-link',
      listClass       : 'tocbot-list',
      isCollapsedClass: 'tocbot-is-collapsed',
      collapsibleClass: 'tocbot-is-collapsible',
      scrollSmooth    : true,
      includeTitleTags: true,
      headingsOffset  : -boardTop,
    }, CONFIG.toc));
    if (toc.find('.toc-list-item').length > 0) {
      toc.css('visibility', 'visible');
    }

    Fluid.events.registerRefreshCallback(function() {
      if ('tocbot' in window) {
        tocbot.refresh();
        var toc = jQuery('#toc');
        if (toc.length === 0 || !tocbot) {
          return;
        }
        if (toc.find('.toc-list-item').length > 0) {
          toc.css('visibility', 'visible');
        }
      }
    });
  });
</script>


  <script src=https://lib.baomitu.com/clipboard.js/2.0.11/clipboard.min.js></script>

  <script>Fluid.plugins.codeWidget();</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/anchor-js/4.3.1/anchor.min.js', function() {
    window.anchors.options = {
      placement: CONFIG.anchorjs.placement,
      visible  : CONFIG.anchorjs.visible
    };
    if (CONFIG.anchorjs.icon) {
      window.anchors.options.icon = CONFIG.anchorjs.icon;
    }
    var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
    var res = [];
    for (var item of el) {
      res.push('.markdown-body > ' + item.trim());
    }
    if (CONFIG.anchorjs.placement === 'left') {
      window.anchors.options.class = 'anchorjs-link-left';
    }
    window.anchors.add(res.join(', '));

    Fluid.events.registerRefreshCallback(function() {
      if ('anchors' in window) {
        anchors.removeAll();
        var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
        var res = [];
        for (var item of el) {
          res.push('.markdown-body > ' + item.trim());
        }
        if (CONFIG.anchorjs.placement === 'left') {
          anchors.options.class = 'anchorjs-link-left';
        }
        anchors.add(res.join(', '));
      }
    });
  });
</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js', function() {
    Fluid.plugins.fancyBox();
  });
</script>


  <script>Fluid.plugins.imageCaption();</script>

  <script  src="/memory_blog/js/local-search.js" ></script>

  <script defer src="/memory_blog/js/leancloud.js" ></script>





<!-- 主题的启动项，将它保持在最底部 -->
<!-- the boot of the theme, keep it at the bottom -->
<script  src="/memory_blog/js/boot.js" ></script>


  

  <noscript>
    <div class="noscript-warning">博客在允许 JavaScript 运行的环境下浏览效果更佳</div>
  </noscript>
</body>
</html>
